I’ve been building Signet, a mutual attestation protocol that cryptographically links a PGP key to an Ethereum address. The whole thesis is self-sovereign identity: you prove who you are, on infrastructure you control, without asking anyone’s permission.

While building it I looked at my own PGP setup and felt like a hypocrite.

My public key lived on keys.openpgp.org. A keyserver I don’t run, don’t control, and can’t guarantee will exist tomorrow. Anyone wanting to verify my signatures had to trust a third party to serve them the right key. That’s not self-sovereign. That’s just outsourcing the problem.

Web Key Directory (WKD) fixes this. It’s a simple protocol that lets email clients and other tools fetch your public key directly from your domain. No keyserver. No third party. Just an HTTP request to a path you control, answered by a file you put there.

Here’s how it works and how to set it up.


What WKD Actually Is

The spec is simple. When someone wants your key for you@yourdomain.com, their client:

  1. Hashes your local part (you) using a specific Z-Base-32 encoding
  2. Makes an HTTPS request to one of two places:
    • Advanced method: https://openpgpkey.yourdomain.com/.well-known/openpgpkey/yourdomain.com/hu/{hash}
    • Direct method: https://yourdomain.com/.well-known/openpgpkey/hu/{hash}

The advanced method is preferred. It uses a dedicated subdomain, which is cleaner and sidesteps potential conflicts with existing web server config. The direct method is the fallback. Implement both.

The file at that path is just your exported public key, in binary (not ASCII-armored).

That’s it. The protocol is not complicated. The implementation is mostly NGINX config and a gpg --export command.


Step 1: Generate Your Hash

GnuPG can compute the WKD hash for you:

gpg --with-wkd-hash --fingerprint you@yourdomain.com

Look for the line that says uid. The hash appears in brackets after your email address, something like dizb37aqa5h4skgu7jf1xjr4q71w4paq. That’s the filename you need.


Step 2: Export Your Key

Export in binary with the minimal flag. Stripping third-party certifications avoids a false-positive revocation warning that some WKD checkers will flag:

gpg --export --export-options export-minimal you@yourdomain.com > {hash}

No --armor. Binary only. The file has no extension.


Step 3: Set Up the Directory Structure

On your server, create the following. I’m using /var/www/yourdomain.com as the web root but adjust for your setup:

/var/www/yourdomain.com/.well-known/openpgpkey/
  policy                          ← empty file, required by spec
  hu/{hash}                       ← direct method key file

/var/www/yourdomain.com/.well-known/openpgpkey/yourdomain.com/
  policy                          ← empty file
  hu/{hash}                       ← advanced method key file

The policy files are empty. Their presence is required by the spec even though they carry no content.

Copy your exported key file to both hu/ directories.


Step 4: DNS

For the advanced method, you need an A record pointing openpgpkey.yourdomain.com at your server. Add that wherever you manage DNS.


Step 5: NGINX Config

Two pieces. First, add a location block to your existing yourdomain.com server block for the direct method:

location /.well-known/openpgpkey/ {
    root /var/www/yourdomain.com;
    default_type application/octet-stream;
    add_header Access-Control-Allow-Origin "*";
    try_files $uri $uri/ =404;
}

Put this before any catch-all redirect rules or you’ll redirect the WKD requests away.

Second, create a new server block for the openpgpkey subdomain:

server {
    listen 443 ssl;
    server_name openpgpkey.yourdomain.com;

    ssl_certificate /etc/letsencrypt/live/openpgpkey.yourdomain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/openpgpkey.yourdomain.com/privkey.pem;

    root /var/www/yourdomain.com;

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

Step 6: TLS for the Subdomain

WKD requires HTTPS. Get a cert for the subdomain:

certbot --nginx -d openpgpkey.yourdomain.com

Test your NGINX config before reloading:

nginx -t && systemctl reload nginx

Step 7: Verify

wkd.dp42.dev will check both methods and tell you exactly what’s passing and what isn’t. You want green on both advanced and direct. If you get a false revocation warning, double-check that you used --export-options export-minimal on your key export.


Why This Matters

Keyservers are not the enemy. keys.openpgp.org is a good service run by good people. But it’s still someone else’s server, someone else’s uptime, someone else’s decision about what to serve.

When you set up WKD, key discovery for your address becomes a function of your domain. If you control your domain, you control your key. Your personal site is yours, not a profile on someone else’s platform. Your key should be yours too, not a record in someone else’s database.

This is a small piece of a larger thing I’ve been thinking about: what does genuinely self-sovereign identity look like? Not a product someone sells you. Not a profile you rent. Cryptographic identity anchored to infrastructure you own, verifiable by anyone, dependent on no one.

Signet is one piece of that. It’s a mutual attestation between a PGP key and an Ethereum address, each signing a commitment to the other. Scry lets you explore the resulting identity graph. WKD is a quieter piece: just making sure that when someone looks for your key, they find it from you.

That’s worth spending a little extra time in your NGINX configs.


Have questions or ran into something weird with your setup? Find me at bendoubleu.eth or via my Scry Profile.