Linux Administration·9 min read·

SSH Certificates Instead of Copying Public Keys

Stop appending pubkeys to authorized_keys on every VPS. Run a small SSH CA, issue short-lived user certs, and revoke access without visiting each host.

NB

Netbay Infrastructure Team

Netbay Engineering

On this page

authorized_keys does not scale. The first box is fine. The twelfth is a graveyard of intern keys, CI deploy keys, and a contractor who left last year. Copying a public key onto every VPS feels simple until you need to revoke one identity everywhere at once. SSH certificates invert the trust: hosts trust a certificate authority, not a pile of public keys. You sign a user's key for a principal, a validity window, and optional force-command constraints. When the window expires, the key is useless even if the file still exists on a laptop.

This is not a replacement for disabling passwords or generating an ed25519 key. Those are already done. Certificates are the next layer: identity with an expiry, issued from a CA you control.

User key plus CA signature, not a copied pubkey USER KEY ed25519 on laptop SSH CA signs, sets expiry USER CERT principal + TTL sshd trusts CA only TrustedUserCAKeys on every host | no per-user authorized_keys sprawl Revoke by expiry, principals, or a KRL. Do not SSH into twelve boxes to delete a line. Host certs (HostCertificate) are the same idea in the other direction.

Build a small CA and keep it offline-ish

A CA is just a key pair that you never use to log in. Generate it on a machine that is not your daily laptop if you can. An air-gapped USB key is ideal; a dedicated admin workstation with a passphrase and a 64-round KDF is acceptable for a small fleet. Never put the CA private key on the VPS that trusts it. That would be signing identity on the same box that accepts identity.

bash
ssh-keygen -t ed25519 -a 64 -f ~/.ssh/ca_user -C "netbay-user-ca"
ssh-keygen -t ed25519 -a 64 -f ~/.ssh/ca_host -C "netbay-host-ca"
ssh-keygen -s ~/.ssh/ca_user -I mei-laptop -n deploy,ubuntu -V +8h -z 1 ~/.ssh/id_ed25519.pub

-I is the key identity, a comment for logs. -n is the principals list: the Unix usernames this cert may assume. -V +8h is an eight-hour validity. -z is a serial, unique per cert, which you will want when you write a revocation list. The output is id_ed25519-cert.pub sitting next to the public key. The OpenSSH client offers it automatically when the private key is used.

Copy only ca_user.pub to each server. The private CA key stays off-box.

Teach sshd to trust the CA

Drop a file in sshd_config.d rather than editing the main config. TrustedUserCAKeys points at the CA public key. AuthorizedPrincipalsFile maps cert principals onto local accounts when you do not want a principal name to equal a Unix name. For a small fleet, matching principal deploy to user deploy is enough.

bash
sudo tee /etc/ssh/sshd_config.d/60-ca.conf > /dev/null <<'EOF'
TrustedUserCAKeys /etc/ssh/ca_user.pub
AuthorizedPrincipalsFile /etc/ssh/auth_principals/%u
PubkeyAuthentication yes
PasswordAuthentication no
EOF
echo deploy | sudo tee /etc/ssh/auth_principals/deploy
sudo chmod 644 /etc/ssh/ca_user.pub
sudo sshd -t && sudo systemctl reload ssh

If AuthorizedPrincipalsFile is unset, sshd accepts a cert whose principals include the target username. That is convenient and a little loose. The principals file lets you grant the same person access to deploy on one box and backup on another without issuing two certs, or refuse a principal that happens to match a local account you never intended to expose.

sshd -T | grep -i ca confirms the daemon loaded the path. A failed login with a cert usually means the principal did not match, the cert expired, or the CA file is the private key by accident. ssh-keygen -L -f id_ed25519-cert.pub prints principals, validity, and extensions. Read it every time you issue.

Expiry, force-command, and source-address

Short validity is the entire operational win. An eight-hour cert for humans, a one-hour cert for CI, a five-minute cert for a break-glass automation. When someone leaves, you stop signing. Existing certs die at expiry. That is cleaner than hunting authorized_keys on every node, including the one that has been powered off for a month.

Extensions tighten a cert further. force-command=internal-sftp turns a key into an SFTP-only identity. source-address=10.0.0.0/24 refuses the cert unless the client comes from that CIDR, which pairs well with a jump host. permit-pty, permit-port-forwarding, and permit-agent-forwarding can be stripped at sign time so a CI cert cannot open a tunnel even if sshd would allow it for humans.

Host certificates close the other direction. Sign the host key, set HostCertificate in sshd, and put the host CA in the client's @cert-authority known_hosts line. Users stop TOFU-clicking through changed host keys when you rebuild a VPS. The first rebuild you do after enabling host certs is the one that proves the setup.

Revocation without visiting every host

Expiry handles the common case. For an emergency revoke, OpenSSH supports a Key Revocation List. ssh-keygen -k -z 1 -s ca_user.pub -f /etc/ssh/revoked.krl serial:1, then RevokedKeys /etc/ssh/revoked.krl in sshd. Distribute the KRL the same way you distribute the CA public key. A one-line AuthorizedKeysFile none on hosts that are cert-only prevents someone from sneaking a raw pubkey back onto disk.

Do not mix models forever. A migration period with both TrustedUserCAKeys and authorized_keys is fine. A year later, leftover raw keys are the same sprawl you started with.

A short takeaway: one CA public key on every sshd, short-lived user certs on laptops, serials you can revoke. That replaces copying pubkeys onto a Lucknow fleet. Issue the first cert against an Ubuntu 24.04 VPS on Netbay — the instance is up in under 60 seconds at netbayhosts.in.

Keep reading

Follow along on a real VPS

Deploy Linux in under 60 seconds

These guides are written against Ubuntu, Debian, and RHEL-family images — the same ones on NetBay.

Deploy an instance