SSH certificate authentication explained

An SSH certificate is a public key signed by a certificate authority (CA) that you control, with a list of allowed user names and an expiry date. Sign a key with ssh-keygen -s ca -I id -n user -V +8h key.pub, then set TrustedUserCAKeys on each server. Servers then accept any valid certificate from that CA, with no authorized_keys entry for each person.

Updated . All commands were run with OpenSSH 10.3 on macOS as client and OpenSSH 10.0 on Debian 13 as server, including wrong principal, expired certificate, source address limit, revocation and host certificates.

What it solves

With plain keys, every server keeps a list of allowed keys, and every laptop keeps a list of host keys. With 50 servers, adding or removing a person means changing 50 files, and old keys tend to stay forever.

With certificates, servers trust one CA key. You sign a person's public key for a set time, and when the certificate expires, the access ends. These are OpenSSH certificates, not the X.509 kind used for websites, and you do not need any outside company or service.

Create the CA

ssh-keygen -t ed25519 -f user_ca -C "user-ca"

This is a normal key pair. Anyone holding the private half user_ca can log in as anyone on every server that trusts it, so give it a strong passphrase and keep it off your daily machine and off the servers. Use a separate CA for hosts, as shown below.

Sign a user's public key

The person sends you id_ed25519.pub, never the private key:

ssh-keygen -s user_ca -I "alice@example.com" -n deploy,alice -V +8h id_ed25519.pub
OptionMeaning
-s user_caThe CA private key used for signing.
-IThe key ID. It is written in the server log at each login, so make it name the person.
-nThe principals: the names this certificate is valid for.
-VThe validity. +8h, +1d, or a range such as -5m:+1d, which also covers a server whose clock runs a little behind.

The result is id_ed25519-cert.pub. Return it to the person, who puts it next to the private key. ssh then finds it by name, with no extra configuration. Kept somewhere else, it needs a CertificateFile line in ~/.ssh/config. To read one:

ssh-keygen -L -f id_ed25519-cert.pub

Without -V, a certificate never expires, which removes much of the benefit.

Make the server trust the CA

Copy user_ca.pub, only the public part, to /etc/ssh/user_ca.pub on the server. Add this to the sshd configuration, then run sudo sshd -t and reload:

TrustedUserCAKeys /etc/ssh/user_ca.pub

The user can now log in without any line in authorized_keys. The server log shows who it was:

Accepted publickey for deploy from 203.0.113.50 port 56276 ssh2: ED25519-CERT SHA256:... ID alice@example.com (serial 0) CA ED25519 SHA256:...

Principals

By default, the server requires the account name to be one of the certificate's principals. A certificate for alice does not open the account deploy.

To use roles instead of account names, list the accepted principals for each account:

AuthorizedPrincipalsFile /etc/ssh/auth_principals/%u

Then /etc/ssh/auth_principals/deploy holds one name per line, for example ops, and you sign with -n ops. With this setting on, the account name stops being accepted automatically: only the names in the file count, and an account with no file refuses certificates.

Limit what a certificate can do

Options are set at signing time. Here the certificate works only from one network and cannot open a terminal:

ssh-keygen -s user_ca -I alice -n ops -V +1h -O source-address=203.0.113.0/24 -O no-pty id_ed25519.pub

-O clear removes all default permissions, and you can then add back the ones you want, for example -O clear -O permit-pty.

When a login fails

The client only says Permission denied (publickey). The reason is in the server log:

Log lineCause
Certificate invalid: name is not a listed principalThe account is not in the certificate's -n list.
Certificate invalid: expiredPast its end date. Sign it again.
Refused by certificate optionsA limit such as source-address does not match.
Authentication key ... revoked by file ...The key is on the revocation list.

Revoke a certificate before it expires

Keep certificates short, so you rarely need this. For the urgent case, create a key revocation list from the public key and copy it to each server:

ssh-keygen -k -f revoked.krl id_ed25519.pub
ssh-keygen -Q -f revoked.krl id_ed25519.pub
RevokedKeys /etc/ssh/revoked.krl

Add later keys to the same list with -u. Take care with this file: if sshd cannot read it, public key login is refused for all users.

For keys without certificates, see how to revoke an SSH key when someone leaves.

Host certificates

Host certificates solve the other direction. Your users stop seeing the unknown host prompt, because they trust a CA and not each host key. Sign the server's public host key with -h, and list the names people use to connect:

ssh-keygen -t ed25519 -f host_ca -C "host-ca"
ssh-keygen -s host_ca -I server1 -h -n server1.example.com -V +52w ssh_host_ed25519_key.pub

On the server, with ssh_host_ed25519_key-cert.pub in /etc/ssh/:

HostKey /etc/ssh/ssh_host_ed25519_key
HostCertificate /etc/ssh/ssh_host_ed25519_key-cert.pub

On each client, add one line to ~/.ssh/known_hosts, with the contents of host_ca.pub:

@cert-authority *.example.com ssh-ed25519 AAAA...

The pattern must match the name you connect with. When it does not, ssh ignores the line and treats the host as unknown, or refuses it if StrictHostKeyChecking is yes. Host certificates also expire, so renew them before that date. See known_hosts explained for how the rest of that file works.

When certificates are worth it

For a few servers and a few people, plain keys are simpler. Certificates begin to pay off when people join and leave often, or when you want short-lived access with a name in the log. The hard part is the process around the CA: who may sign, where the private key lives, and how a person gets a new certificate each day. ssh-keygen gives you the signing, not that process.

Questions

What is the difference between an SSH key and an SSH certificate?

A certificate is a public key signed by a CA, with a list of allowed user names and an expiry date. A server that trusts the CA accepts it without an authorized_keys entry for that key.

How do I check when an SSH certificate expires?

Run ssh-keygen -L -f id_ed25519-cert.pub. The Valid line shows the start and the end of the validity period.

Do SSH certificates replace authorized_keys?

For certificate logins, yes: the server only needs TrustedUserCAKeys. Plain keys listed in authorized_keys keep working next to them.

Sources

More in Keys and login

All guides