Web Key Directory (WKD): publish and find keys by email

Updated November 2026

A Web Key Directory (WKD) lets the owner of a mail domain publish its users' public keys on its own HTTPS web server. Anyone who knows an address like alice@example.org can then fetch the current key with gpg --locate-keys alice@example.org, without a central keyserver. Setting one up takes a folder of static files and a few lines of web server configuration.

How a lookup works

WKD is described in the Internet-Draft draft-koch-openpgp-webkey-service. As of November 2026 the current version is -22, from July 2026. A client turns the email address into a URL in four steps:

  1. Take the part before the @ (the local part) and change ASCII capital letters to lowercase. Joe.Doe becomes joe.doe.
  2. Hash it with SHA-1.
  3. Encode the 160-bit hash with z-base-32. The result is always 32 characters, here iy9q119eutrkn8s1mk4r39qejnbu3n5q.
  4. Build the URL with the domain and that string. The original local part goes in a parameter named l.

There are two URL forms. Clients must try the advanced method first. It uses a dedicated host called openpgpkey in the mail domain:

https://openpgpkey.example.org/.well-known/openpgpkey/example.org/hu/iy9q119eutrkn8s1mk4r39qejnbu3n5q?l=Joe.Doe

Only if the name openpgpkey.example.org does not exist in DNS do clients fall back to the direct method on the domain itself:

https://example.org/.well-known/openpgpkey/hu/iy9q119eutrkn8s1mk4r39qejnbu3n5q?l=Joe.Doe

The server returns the key in binary form, not ASCII armor, ideally with the type application/octet-stream. The key must carry a user ID with that address. The draft also requires a policy file at …/.well-known/openpgpkey/example.org/policy (advanced) or …/.well-known/openpgpkey/policy (direct). An empty file is enough.

The SHA-1 step does not hide anything. It only turns any local part into a safe, fixed-length file name. Clients must keep only the user ID they asked for and drop the others. They must follow redirects only to other https URLs, and must never answer a password prompt (HTTP 401), because WKD data is public.

You can print the hash and the URL for any address:

$ gpg-wks-client --print-wkd-hash Joe.Doe@Example.ORG
iy9q119eutrkn8s1mk4r39qejnbu3n5q joe.doe@example.org
$ gpg-wks-client --print-wkd-url Joe.Doe@Example.ORG
https://openpgpkey.example.org/.well-known/openpgpkey/example.org/hu/iy9q119eutrkn8s1mk4r39qejnbu3n5q?l=joe.doe

On Debian and Ubuntu, gpg-wks-client comes in its own package: sudo apt install gpg-wks-client.

Find a key with WKD

GnuPG has supported WKD since 2.1.12 (2016) and has used it by default since 2.1.23 (2017). Its default --auto-key-locate setting is local,wkd. So when you encrypt to an address and have no key for it, gpg asks the address's Web Key Directory. To look a key up on purpose:

gpg --locate-keys torvalds@kernel.org

--locate-keys uses a matching key from your keyring if you have one. --locate-external-keys always asks the network, which is useful for refreshing a key. To force a WKD-only lookup:

gpg --auto-key-locate clear,nodefault,wkd --locate-external-keys torvalds@kernel.org

kernel.org uses the direct method. A lookup in November 2026 printed:

gpg: key 79BE3E4300411886: public key "Linus Torvalds <torvalds@kernel.org>" imported
gpg: Total number processed: 1
gpg:               imported: 1
gpg: automatically retrieved 'torvalds@kernel.org' via WKD
pub   rsa2048 2011-09-20 [SC]
      ABAF11C65A2970B130ABE3C479BE3E4300411886
uid           [ unknown] Linus Torvalds <torvalds@kernel.org>
sub   rsa2048 2011-09-20 [E]

WKD proves that the key came from the domain's web server over HTTPS. That is good protection for everyday use, but it is not a personal check. For high-stakes use, still compare the fingerprint through another channel. Outside GnuPG, the GnuPG wiki lists WKD support in GpgOL for Outlook (since 2.2.0), KMail (since 5.6), Claws Mail, Mailvelope (since 3.0.0) and OpenKeychain-based Android mail apps.

Publish keys as static files

You need a web server with a trusted TLS certificate for the domain, or for openpgpkey. plus the domain. GnuPG's gpg-wks-client --install-key builds the folder for you from your keyring. The example below uses a key for alice@example.org. Create the output folder first, because the tool will not create it:

mkdir openpgpkey
gpg --list-options show-only-fpr-mbox -k @example.org | gpg-wks-client -v --install-key

The first command lists every key in your keyring with an @example.org address, as "fingerprint address" pairs. The second writes one file per address:

gpg-wks-client: using key with user id 'Alice Example <alice@example.org>'
gpg-wks-client: directory 'openpgpkey/example.org' created
gpg-wks-client: directory 'openpgpkey/example.org/hu' created
gpg-wks-client: policy file 'openpgpkey/example.org/policy' created
gpg-wks-client: key F1C81BEF8B0CA5139E91229CD6B1FCF01404A8FB published for 'alice@example.org'

The result follows the advanced layout:

openpgpkey/
└── example.org/
    ├── policy
    └── hu/
        └── kei1q4tipxxu1yj79k9kfukdhfy631xe

Each file holds the key with only the user ID for that address, as the draft requires. Revocations are added by default (--add-revocs). If a key also has addresses at other domains, the tool creates a folder for each of those domains too, for example openpgpkey/other.example/. Publish only the folder for the domain you serve. Then copy the folder to the web server:

sudo mkdir -p /var/www/openpgpkey/.well-known
sudo cp -r openpgpkey /var/www/openpgpkey/.well-known/

Run the build again, and copy the files again, whenever a key changes: a new subkey, a longer expiry date or a revocation. A stale file keeps serving the old key.

nginx configuration

A separate HTTPS server block for the advanced method. The empty types block makes nginx send every file in the folder as application/octet-stream:

server {
    listen 443 ssl;
    listen [::]:443 ssl;
    server_name openpgpkey.example.org;

    ssl_certificate     /etc/ssl/openpgpkey.example.org/fullchain.pem;
    ssl_certificate_key /etc/ssl/openpgpkey.example.org/privkey.pem;

    root /var/www/openpgpkey;

    location /.well-known/openpgpkey/ {
        types { }
        default_type application/octet-stream;
        add_header Access-Control-Allow-Origin "*" always;
        autoindex off;
        try_files $uri =404;
    }

    location / {
        return 404;
    }
}

For the direct method, put the same location /.well-known/openpgpkey/ block inside the existing server block for example.org, whose root holds the .well-known/openpgpkey/hu/ folder. The Access-Control-Allow-Origin header is optional. It lets browser-based tools, such as web mail extensions and online WKD checkers, read the key; kernel.org's directory sends it, for example. Check and reload:

sudo nginx -t
sudo systemctl reload nginx

Apache configuration

The same for Apache httpd 2.4. ForceType is a core directive; the Header line needs mod_headers:

<VirtualHost *:443>
    ServerName openpgpkey.example.org
    DocumentRoot /var/www/openpgpkey

    SSLEngine on
    SSLCertificateFile    /etc/ssl/openpgpkey.example.org/fullchain.pem
    SSLCertificateKeyFile /etc/ssl/openpgpkey.example.org/privkey.pem

    <Directory "/var/www/openpgpkey/.well-known/openpgpkey">
        Options -Indexes
        ForceType application/octet-stream
        Header always set Access-Control-Allow-Origin "*"
        Require all granted
    </Directory>
</VirtualHost>

On Debian or Ubuntu, save it as /etc/apache2/sites-available/openpgpkey.conf, then:

sudo a2enmod ssl headers
sudo a2ensite openpgpkey
sudo apachectl configtest
sudo systemctl reload apache2

No web server? Use keys.openpgp.org

keys.openpgp.org offers WKD as a service. Point the openpgpkey name of your domain at it with a CNAME record:

openpgpkey.example.org. 300 IN CNAME wkd.keys.openpgp.org.

Addresses at your domain that have been verified on keys.openpgp.org then become available over WKD. You can check the DNS part with:

curl 'https://wkd.keys.openpgp.org/status/?domain=openpgpkey.example.org'

The GnuPG wiki calls such central services a stopgap. The service, not your domain, then decides which key is served. See keys.openpgp.org for uploading and verifying.

Web Key Service for mail providers

Static files suit a person or a small team. A mail provider with many users can run a Web Key Service (WKS) instead. That is the update protocol in the same draft, implemented by GnuPG's gpg-wks-server. A user's mail client sends the key to the provider's submission address. The provider mails back an encrypted confirmation request, and the key is published once the user answers it. gpg-wks-client --supported user@example.org tells you whether a domain offers this. According to the GnuPG wiki, mail providers such as Posteo and Proton already publish their users' keys in a Web Key Directory.

Test your setup

Use a throwaway home directory so the test does not touch your keyring and cannot use a key you already have:

gpg --homedir "$(mktemp -d)" --verbose --locate-keys alice@example.org

Look for automatically retrieved 'alice@example.org' via WKD. A second check, with more detail:

gpg-wks-client --check -v alice@example.org

Check the raw HTTP response, the content type and the policy file:

curl -sI 'https://openpgpkey.example.org/.well-known/openpgpkey/example.org/hu/kei1q4tipxxu1yj79k9kfukdhfy631xe?l=alice'
curl -sI 'https://openpgpkey.example.org/.well-known/openpgpkey/example.org/policy'

Both should return status 200, and the key should come back as application/octet-stream. A request for https://openpgpkey.example.org/.well-known/openpgpkey/example.org/hu/ should not list the files.

Common mistakes

Sources