Single user WKD setup

Written by Pranav Chakkarwar . Published 15 Jun 2025

A WKD server makes it easy to find genuine PGP keys and avoid fake ones. But, maintaining a WKD server for a one or just a few email addresses would be an overkill.

Fortunately, there are simpler alternatives:

I like the first option because it gives us more control over how our keys are handled and we only need a webserver FTW! So, let’s self-host!

Create a PGP key pair

Quickly generate a key

gpg --generate-key

OR

Generate a key with advanced options

gpg --full-generate-key

View metadata of your key

gpg --list-keys

Example metadata

pub  rsa4096 2025-05-10 [SC] [expires: 2035-05-10]
     0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
uid  [ultimate] Name <name@example.com>
sub  rsa4096 2025-05-10 [E] [expires: 2035-05-10]

0000 0000 ... is the KEY_FINGERPRINT

Export the public key

The public key should be saved unarmored, and named after the WKD hash of itself.

View the WKD hash of your key

gpg --with-wkd-hash --fingerprint KEY_FINGERPRINT

Example output for the WKD hash cmd

uid  [ultimate] Name <name@example.com>
     WKD_HASH@example.com

The next cmd will save the public key in your Downloads folder, named after the WKD_HASH of your key.

Don’t forget to replace KEY_FINGERPRINT and WKD_HASH with the fingerprint and WKD hash of your key.

Delete whitespaces from the fingerprint, if any.

gpg --no-armor --export --fingerprint KEY_FINGERPRINT > /home/$USER/Downloads/WKD_HASH

Web server configuration

I’ll assume you’ve copied the exported key file to the server.

I use Caddy, so I’ll focus on it’s configuration and skip tutorials for other web servers. Sorry.

Add this to your Caddyfile after replacing example.com and WKD_HASH with your domain and your key’s WKD hash.

openpgpkey.example.com {

    header /.well-known/openpgpkey/example.com/policy Content-Type text/plain
    respond /.well-known/openpgpkey/example.com/policy `protocol-version 5`

    handle_path /.well-known/openpgpkey/example.com/hu/WKD_HASH {
        header Content-Type application/octet-stream
        root * /path/to/wkd/file/WKD_HASH
        file_server
    }
}

Comments on Caddyfile configuration:

As per WKD’s RFC draft, the webserver must serve a policy file at /.well-known/openpgpkey/example.com/policy, with headers: Content-Type text/plain and protocol-version 5. However, this file can be empty.

The webserver must serve the unarmored public key file at /.well-known/openpgpkey/example.com/hu/WKD_HASH with header Content-Type application/octet-stream.

Testing the WKD setup

When set up correctly, any WKD-compatible client should be able to retrieve the public key from the server over an encrypted TLS connection, potentially with DNSSEC verification against the domain.

You can test your setup using the --locate-keys option in gpg.

gpg --locate-keys whatever@domainname.tld

It should say new keys imported or output the key’s metadata if it already exists on your machine.

As per my test, the OpenKeychain app on Android could correctly fetch the key from my server using just my email address. Third-party keyserver search was turned off during the test.

It’s almost as if all this setup is just a way to bypass using the --fetch-keys option.

LMFAO

gpg --fetch-keys https://link-to/public-key.asc