Running your own OpenPGP keyserver with Hockeypuck

Updated November 2026

Most people and organizations no longer need their own keyserver. A Web Key Directory or keys.openpgp.org covers the usual needs. If you do need one, for an internal network or to join the public sync network, the practical choice in November 2026 is Hockeypuck. This page shows how to set it up and what you take on by running it.

Do you need a keyserver?

Ask what problem you are solving first:

Software choices

OpenPGP keyserver software, as of November 2026
SoftwareLanguage and storageStatus
HockeypuckGo, PostgreSQL 13 or later (tested up to 18)Maintained. Latest release 2.4.1 (August 31, 2026). Syncs with other Hockeypuck servers over the SKS reconciliation protocol. AGPLv3.
HagridRust, SQLite (since v2.0)The software of keys.openpgp.org. It verifies email addresses before publishing them. Its README says it is built only for that service and "not maintained or officially supported as deployable software."
SKSOCaml, Berkeley DBObsolete. The last commit to its repository dates from March 2021, the public pool closed in June 2021, and its design cannot delete data.

Hagrid is open source, and you can build it to study it: cargo run with a copied Rocket.toml starts it on port 8080, and it needs a working local sendmail for verification mails. For production, its authors point you to the running service, not to self-hosting. The rest of this page covers Hockeypuck.

The Hockeypuck project recommends its docker-compose/standalone setup for most users. It bundles Hockeypuck, PostgreSQL, HAProxy (TLS termination and client allow/deny lists), Let's Encrypt certificates and Prometheus monitoring. It is tested on Ubuntu 24.04 and Debian 13. Before you start:

Always deploy a release tag, not master, which the project calls alpha quality:

git clone https://github.com/hockeypuck/hockeypuck
cd hockeypuck
git checkout 2.4.1
cd contrib/docker-compose/standalone
./mksite.bash

./mksite.bash creates a .env file with a random database password. Edit it and set at least these values (no quotes, except around ALIAS_FQDNS):

FQDN=keys.example.com
ALIAS_FQDNS=""
EMAIL=admin@example.com
FINGERPRINT=0x0123456789ABCDEF0123456789ABCDEF01234567

Then generate the configuration, build, get a certificate and start:

./mkconfig.bash
docker compose build
./init-letsencrypt.bash
docker compose up -d
docker compose logs -f hockeypuck

Use docker-compose instead of docker compose if you have the older Compose v1. Hockeypuck's own settings are in hockeypuck/etc/hockeypuck.conf. After you change them, run docker compose restart.

Loading existing keys

A new server starts empty. That is fine for a private server: upload your organization's keys and you are done. To peer with public servers, you first need a recent dump of their keys. The project's ./sync-sks-dump.bash now stops with the message that "public keyserver dumps are no longer maintained" to prevent abuse. The project lists dump sources that give out rsync passwords on request. Once you have access:

export KEYDUMP=rsync://username:password@rsync.example.com/hockeypuck/dump
./sync-sks-dump.bash
docker compose up -d

Loading takes several hours or even days. When the key count stops climbing, ./clean-sks-dump.bash deletes the dump files and frees the disk space.

Install from source with systemd

If you prefer packages you manage yourself, build Hockeypuck directly. Version 2.4.1 needs Go 1.26 or later. Debian 13's golang-go is 1.24, which is too old, so install Go from go.dev first. Then, on Debian or Ubuntu:

sudo apt install git make postgresql
git clone https://github.com/hockeypuck/hockeypuck
cd hockeypuck
git checkout 2.4.1
make build
sudo make install

make install puts hockeypuck, hockeypuck-load, hockeypuck-dump and the other tools in /usr/bin. It also puts templates and web files in /var/lib/hockeypuck and a sample config in /etc/hockeypuck/. Create a system user, its prefix-tree folder and the database:

sudo adduser --system --group --home /var/lib/hockeypuck hockeypuck
sudo chown -R hockeypuck:hockeypuck /var/lib/hockeypuck
sudo -u hockeypuck mkdir -p /var/lib/hockeypuck/recon.db
sudo -u postgres createuser hockeypuck
sudo -u postgres createdb -O hockeypuck hkp

Write /etc/hockeypuck/hockeypuck.conf. This example is a private server behind a TLS reverse proxy. It serves only self-signatures and does not peer:

[hockeypuck]
loglevel="INFO"
indexTemplate="/var/lib/hockeypuck/templates/index.html.tmpl"
vindexTemplate="/var/lib/hockeypuck/templates/index.html.tmpl"
statsTemplate="/var/lib/hockeypuck/templates/stats.html.tmpl"
responseTemplate="/var/lib/hockeypuck/templates/response.html.tmpl"
webroot="/var/lib/hockeypuck/www"
hostname="keys.example.com"
contact="0123456789ABCDEF0123456789ABCDEF01234567"

[hockeypuck.hkp]
bind="127.0.0.1:11371"
logRequestDetails=false

[hockeypuck.hkp.queries]
selfSignedOnly=true

[hockeypuck.openpgp.db]
driver="postgres-jsonb"
dsn="database=hkp host=/var/run/postgresql port=5432 user=hockeypuck sslmode=disable"

[hockeypuck.conflux.recon]
reconAddr="127.0.0.1:11370"
allowCIDRs=["127.0.0.1/8"]

[hockeypuck.conflux.recon.leveldb]
path="/var/lib/hockeypuck/recon.db"

The project's Debian packaging uses this unit file. Save it as /etc/systemd/system/hockeypuck.service:

[Unit]
Description=hockeypuck
After=network.target postgresql.service
Wants=postgresql.service

[Service]
User=hockeypuck
Group=hockeypuck
LimitNOFILE=49152
Environment=HOME=/var/lib/hockeypuck
WorkingDirectory=/var/lib/hockeypuck
ExecStart=/usr/bin/hockeypuck -config /etc/hockeypuck/hockeypuck.conf
ExecReload=/bin/kill -USR1 $MAINPID
Restart=on-failure

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now hockeypuck
sudo journalctl -u hockeypuck -f

Put a TLS reverse proxy in front so clients can use hkps://keys.example.com. Hockeypuck can also serve HKPS itself, with a [hockeypuck.hkps] section holding bind, cert and key. A minimal nginx server block:

server {
    listen 443 ssl;
    listen [::]:443 ssl;
    server_name keys.example.com;

    ssl_certificate     /etc/ssl/keys.example.com/fullchain.pem;
    ssl_certificate_key /etc/ssl/keys.example.com/privkey.pem;

    client_max_body_size 2m;

    location / {
        proxy_pass http://127.0.0.1:11371;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

Hockeypuck accepts keys up to 1 MiB by default (maxKeyLength), and an upload is a little larger once it is form-encoded. nginx's default request limit is exactly 1 MB, so the example raises it. To import a dump on this kind of install, stop the service and run, as the hockeypuck user:

sudo systemctl stop hockeypuck
sudo -u hockeypuck hockeypuck-load -config /etc/hockeypuck/hockeypuck.conf /path/to/dump/*.pgp
sudo systemctl start hockeypuck

Check that it works

The stats page shows the software, version, key count and peers:

curl -s 'https://keys.example.com/pks/lookup?op=stats&options=mr'

The reply is JSON with fields such as "version", "hostname", "software":"Hockeypuck" and "Total" (the number of keys). Then send a key and fetch it back with GnuPG:

gpg --keyserver hkps://keys.example.com --send-keys 0123456789ABCDEF0123456789ABCDEF01234567
gpg --keyserver hkps://keys.example.com --recv-keys 0123456789ABCDEF0123456789ABCDEF01234567
curl -s 'https://keys.example.com/pks/lookup?op=index&options=mr&search=alice@example.org'

To make the server the default for your users, put keyserver hkps://keys.example.com in their ~/.gnupg/dirmngr.conf. The HKP page explains the lookup URLs.

Peering with other servers

Peers reconcile their key sets over port 11370 and then fetch the differences over HKP. Add one section per partner, using only letters, digits and underscores in the name. Allow inbound recon from the partner, and remove the 127.0.0.1 restriction on reconAddr:

[hockeypuck.conflux.recon]
reconAddr=":11370"

[hockeypuck.conflux.recon.partner.keys_partner_example]
httpAddr="keys.partner.example:11371"
reconAddr="keys.partner.example:11370"

Load a dump before you join, so you do not have to sync millions of keys one by one. Peer only with servers on the same Hockeypuck minor version, such as 2.4.x with 2.4.x. The project does not guarantee sync between different minor versions. If a peer's key count drifts far from yours for days, the standalone setup includes ./ptree-rebuild.bash to rebuild the prefix tree. Hockeypuck can also push updates one way to older servers by email or HKP, with a [hockeypuck.pks] section. That is the same idea the 1990s email keyservers used, described on the email keyserver page.

Sources