How to Use SSH Certificates Instead of Keys (One CA, No More authorized_keys Files)

With four machines and three laptops, authorized_keys files are a maintenance job: a new laptop means touching every server; a lost one means finding and removing its key everywhere. SSH certificates replace that with a small certificate authority: you sign each laptop's key once, with an expiry and the usernames it may use; every server trusts the CA with a single line in sshd_config. The same trick for host keys ends the "authenticity of host can't be established" prompt for good. It's built into OpenSSH; no software to install.
Quick reference
| Task | Command |
|---|---|
| Make the CA (on a safe machine) | ssh-keygen -t ed25519 -f ~/ssh-ca/user_ca -C "home user CA" |
| Sign a laptop's key | ssh-keygen -s user_ca -I john-laptop -n john,root -V +52w id_ed25519.pub |
| Servers trust the CA | TrustedUserCAKeys /etc/ssh/user_ca.pub in sshd_config |
| Sign a host key | ssh-keygen -s host_ca -I homeserver -h -n homeserver,192.168.1.50 -V +520w ssh_host_ed25519_key.pub |
| Clients trust host CA | @cert-authority * ssh-ed25519 AAAA… in ~/.ssh/known_hosts |
| Revoke | RevokedKeys /etc/ssh/revoked_keys + ssh-keygen -k |
| Inspect | ssh-keygen -L -f id_ed25519-cert.pub |
1. Create the CA
On your most trusted machine (the laptop you use for admin, or an offline box), not on a server:
mkdir -p ~/ssh-ca && cd ~/ssh-ca
ssh-keygen -t ed25519 -f user_ca -C "home user CA"
ssh-keygen -t ed25519 -f host_ca -C "home host CA"
Two CAs: one signs users (laptops), one signs hosts (servers). Passphrases on both. Back up the private keys encrypted, off the server — whoever holds user_ca can log in anywhere. Only the .pub halves go out.
2. Sign a user key
Each laptop keeps its own key pair (3). Send its id_ed25519.pub to the CA machine and sign:
ssh-keygen -s user_ca -I john-laptop -n john,root -V +52w ~/incoming/id_ed25519.pub
- -I john-laptop — an identity, logged on every login (journalctl -u ssh shows which certificate connected — tutorial 71 gets a name instead of a fingerprint).
- -n john,root — the usernames this certificate may log in as (the principals).
- -V +52w — valid for a year; -V +4w for a contractor.
It produces id_ed25519-cert.pub. Copy it back to the laptop next to the key (~/.ssh/id_ed25519-cert.pub); ssh picks it up automatically. ssh-keygen -L -f shows what's inside.
3. Servers trust the CA
On every server (and in the Ansible hardening role, 140):
sudo cp user_ca.pub /etc/ssh/user_ca.pub
echo "TrustedUserCAKeys /etc/ssh/user_ca.pub" | sudo tee -a /etc/ssh/sshd_config
sudo sshd -t && sudo systemctl restart ssh
Test from the laptop: ssh john@server — in, with no entry in authorized_keys. Keep authorized_keys for now as a fallback; remove it once every laptop has a certificate (AuthorizedKeysFile none for the strict version).
A certificate can also carry restrictions: -O no-port-forwarding, -O source-address=192.168.1.0/24, -O force-command=/usr/local/bin/backup-only.sh — a backup client's certificate that can run only the backup.
4. Host certificates: no more fingerprint prompts
Sign each server's host key:
scp john@server:/etc/ssh/ssh_host_ed25519_key.pub ~/incoming/homeserver.pub
ssh-keygen -s host_ca -I homeserver -h -n homeserver,homeserver.lan,192.168.1.50 -V +520w ~/incoming/homeserver.pub
scp ~/incoming/homeserver-cert.pub john@server:/tmp/
On the server:
sudo mv /tmp/homeserver-cert.pub /etc/ssh/ssh_host_ed25519_key-cert.pub
echo "HostCertificate /etc/ssh/ssh_host_ed25519_key-cert.pub" | sudo tee -a /etc/ssh/sshd_config
sudo systemctl restart ssh
On every laptop, one line in ~/.ssh/known_hosts (or /etc/ssh/ssh_known_hosts for all users):
@cert-authority *.lan,192.168.1.*,192.168.2.* ssh-ed25519 AAAA...the host_ca.pub key...
Now a rebuilt server (74, 172) with a newly signed host key is trusted immediately — no "REMOTE HOST IDENTIFICATION HAS CHANGED", no blind yes. The prompt you've been clicking through since tutorial 2 was the attack surface for a man-in-the-middle; this closes it.
5. Revoke
A laptop is lost:
ssh-keygen -k -f revoked_keys -s user_ca -z 1 ~/incoming/lost-laptop-cert.pub
Copy revoked_keys to every server (Ansible, one run) with RevokedKeys /etc/ssh/revoked_keys in sshd_config. Or simply rely on the expiry — -V +4w certificates make revocation rarely necessary. Keep a list of what you've signed (-I identities, serials via -z) in BookStack (88).
6. Renewal
A year later, re-sign the same public key with a new -V. Script it: a tiny signing script on the CA machine, or step-ca (146) which has an SSH CA mode with web-based enrolment and automatic renewal — the same tool, both certificate worlds.
Troubleshooting
"Permission denied (publickey)" with a certificate. The principal (-n) doesn't include the username you're logging in as; or the certificate expired (ssh-keygen -L); or the server lacks TrustedUserCAKeys. ssh -v says which.
Host certificate ignored, still prompting. The @cert-authority pattern doesn't match how you typed the host (name vs IP); the -n list on the host cert must include it too.
sshd -t error about the cert file. The -cert.pub must sit next to the matching private host key and be readable.
Lost user_ca. Make a new one, re-sign every laptop, replace user_ca.pub on every server. Painful; that's why it's backed up encrypted.
Wrapping up
Two CA keys on a safe machine, user certificates with principals and expiry, one TrustedUserCAKeys line per server, host certificates so fingerprints are verified instead of accepted. New laptop: one signature. Lost laptop: one revocation. Rebuilt server: trusted at once. The last manual piece of SSH administration goes away.
