How-to guide · Labs and advanced

How to use SSH certificates instead of known_hosts

In one sentence

You will have OpenSSH host and user certificates signed by your own CAs, so clients trust servers without first-use prompts and servers trust short-lived user certificates.

Task
Labs and advanced
Platforms
  • Linux
  • macOS
  • Windows
Who it is for
Level
Practitioner · 6 steps
On this page
  1. Before you start
  2. Steps
  3. 1. Create a host CA and a user CA
  4. 2. Sign each server’s host key
  5. 3. Make clients trust the host CA
  6. 4. Issue short-lived user certificates
  7. 5. Make servers trust the user CA
  8. 6. Revoke when needed
  9. Check it worked
  10. If something goes wrong
  11. Why this works
  12. Go deeper

Before you start

  • OpenSSH on clients and servers. Any current version works; this guide was tested with OpenSSH 8.9 and 9.9.
  • Administrator access to the servers’ sshd_config.
  • A safe place for two CA private keys: an offline machine, a hardware token or a signing service. Whoever holds the user CA key can log in anywhere that trusts it.

Why this matters: plain SSH relies on trust on first use. The first connection asks “The authenticity of host … can’t be established”, and almost everyone types yes. Each server also keeps its own authorized_keys list, which rarely gets cleaned up. An SSH certificate fixes both: a CA vouches for host and user keys, with names and an expiry date.

SSH certificates are not X.509 certificates. They use OpenSSH’s own simpler format, but the idea is the same: a trusted signer binds a key to a name for a limited time.

Steps

1. Create a host CA and a user CA

Use two separate CAs, so that the key that vouches for servers can never log anyone in:

ssh-keygen -t ed25519 -f host_ca -C "Example host CA"
ssh-keygen -t ed25519 -f user_ca -C "Example user CA"

Give both a strong passphrase when asked. host_ca.pub and user_ca.pub are public and go to every machine; the private halves stay on the signing machine.

2. Sign each server’s host key

Copy the server’s public host key to the signing machine, sign it with -h (host certificate), and list every name clients use with -n:

ssh-keygen -s host_ca -I web01.lab.test -h -n web01.lab.test,localhost -V +52w ssh_host_ed25519_key.pub
Signed host key ssh_host_ed25519_key-cert.pub: id "web01.lab.test" serial 0 for web01.lab.test,localhost valid from 2026-10-20T04:36:00 to 2027-10-19T04:37:01

Our lab server also answered as localhost, so that name is in the list; a real server lists its short name, its full name and any aliases. Copy ssh_host_ed25519_key-cert.pub back to /etc/ssh/ on the server and add one line to /etc/ssh/sshd_config:

HostCertificate /etc/ssh/ssh_host_ed25519_key-cert.pub

3. Make clients trust the host CA

Add one @cert-authority line to known_hosts, globally in /etc/ssh/ssh_known_hosts or per user in ~/.ssh/known_hosts (on Windows, %USERPROFILE%\.ssh\known_hosts). The pattern says which host names this CA may vouch for:

@cert-authority *.lab.test ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA… Example host CA

The rest of the line is the content of host_ca.pub. From now on, any server whose certificate is signed by this CA and lists the name you typed is accepted without a prompt, including servers that were rebuilt with new keys.

4. Issue short-lived user certificates

Users keep their own key pair. The CA signs the public key with a key ID (for logs), the principals (account names it may use) and a short validity. A unique serial number with -z makes revocation by serial possible:

ssh-keygen -s user_ca -I alice@example.com -n alice,deploy -V +8h -z 1001 id_ed25519.pub
ssh-keygen -L -f id_ed25519-cert.pub
id_ed25519-cert.pub:
        Type: ssh-ed25519-cert-v01@openssh.com user certificate
        Public key: ED25519-CERT SHA256:fv87OrfS8dOFa/vp4pMtWjrbvCMrCb57fZ4MQoox024
        Signing CA: ED25519 SHA256:5xODG4rQWAa3niuhYfPuIAzl9VpmBr4/L4I5ZXJeOTY (using ssh-ed25519)
        Key ID: "alice@example.com"
        Serial: 1001
        Valid: from 2026-10-20T04:36:00 to 2026-10-20T12:37:33
        Principals: 
                alice
                deploy
        Critical Options: (none)
        Extensions: 
                permit-X11-forwarding
                permit-agent-forwarding
                permit-port-forwarding
                permit-pty
                permit-user-rc

The client finds id_ed25519-cert.pub automatically when it sits next to id_ed25519. Eight hours covers a working day; the next day the user gets a new certificate. Restrict what a certificate allows with -O, for example -O clear -O permit-pty to drop forwarding.

5. Make servers trust the user CA

Copy user_ca.pub to each server and add to sshd_config:

TrustedUserCAKeys /etc/ssh/user_ca.pub
AuthorizedPrincipalsFile /etc/ssh/principals/%u
RevokedKeys /etc/ssh/revoked.krl
  • With TrustedUserCAKeys alone, a certificate may log in to an account whose name is one of its principals.
  • AuthorizedPrincipalsFile changes that to an explicit list per account: /etc/ssh/principals/deploy might contain deploy and oncall. Accounts without a file accept no certificates.
  • RevokedKeys points at a key revocation list (step 6). Create it before you add the line: sshd refuses keys when the file cannot be read.

Check the configuration and reload: sudo sshd -t && sudo systemctl reload ssh (the service is called sshd on some distributions).

6. Revoke when needed

A key revocation list (KRL) can revoke certificates by serial number, by key ID or by key. Revoke serial 1001 from the user CA, then check it next to a second certificate for the same key with another serial (id_alice-cert.pub):

echo "serial: 1001" > revoke.txt
ssh-keygen -k -f revoked.krl -s user_ca.pub revoke.txt
ssh-keygen -Q -f revoked.krl id_ed25519-cert.pub id_alice-cert.pub
Revoking from revoke.txt
id_ed25519-cert.pub (alice laptop): REVOKED
id_alice-cert.pub (alice laptop): ok

Distribute revoked.krl to every server. With short-lived certificates, most departures need no revocation at all: you simply stop issuing.

Check it worked

Connect with verbose output and look for the certificate lines. Our lab server listened on port 2222, its known_hosts held only the @cert-authority line (with the pattern [localhost]:2222), and the account’s principals file allowed deploy:

ssh -v -p 2222 localhost true
debug1: Server host certificate: ssh-ed25519-cert-v01@openssh.com SHA256:amKQ68HAp56XNHO9957Og3G3CU+xr82HdvwD3VFigmE, serial 0 ID "web01.lab.test" CA ssh-ed25519 SHA256:r/i7Yd+fhYPjPqVrBuLr6q3yV9ng3JWbyT0PFPlQr8Q valid from 2026-10-20T04:36:00 to 2027-10-19T04:37:01
debug1: Host '[localhost]:2222' is known and matches the ED25519-CERT host certificate.
debug1: Server accepts key: …/id_ed25519 ED25519-CERT SHA256:fv87OrfS8dOFa/vp4pMtWjrbvCMrCb57fZ4MQoox024 explicit
Authenticated to localhost ([127.0.0.1]:2222) using "publickey".

The host was trusted through its certificate, with no prompt, and the server accepted the user’s certificate rather than a plain key.

If something goes wrong

On the server, sshd logs the reason a certificate was refused (journalctl -u ssh or /var/log/auth.log). The client only says Permission denied (publickey). Real messages from our lab:

  • Certificate does not contain an authorized principal: the certificate’s principals do not match the account, or the account’s AuthorizedPrincipalsFile. Re-issue with the right -n, or update the file.
  • Certificate invalid: expired: issue a new certificate. If they expire mid-task, make the validity a little longer, not months.
  • … revoked by file /etc/ssh/revoked.krl: the certificate’s serial, key ID or key is in the KRL.
  • No ED25519 host key is known for … and you have requested strict checking on the client: no @cert-authority line matches the host name you typed, or the host certificate does not list that name. Compare ssh-keygen -L -f on the host certificate with the name in your command.
  • The prompt still appears for some servers: those servers present no certificate (check HostCertificate and reload sshd), or the name you typed is missing from the certificate’s principals. Old plain entries in known_hosts can also keep the client on the plain key; remove them with ssh-keygen -R <host>.

Why this works

A certificate is a CA’s signature over a public key, a name and a validity period (binding identity to keys). Clients trust the host CA’s key instead of each server’s key, and servers trust the user CA instead of copies of every user’s key, exactly as browsers trust a root instead of every website. Keeping the CA keys offline and the lifetimes short follows the same logic as a private CA, and the KRL plays the role of a CRL (revocation).

Go deeper

Early edition. Checked against the standards; an independent expert review is in progress. Found a mistake? Tell us.

Last reviewed Edition 0.9