← blog · October 18, 2026

SSH Certificate Authority: Short-Lived Access Instead of authorized_keys, Principals and Host Certificates

As distributed key files grow, nobody knows who can log in where. How to set up short-lived, role-based user certificates and host certificates that end fingerprint prompts with OpenSSH's built-in certificate authority, and the pitfalls to watch for.

SSH access decays quietly as a team grows. Nobody knows whose keys sit in each server's authorized_keys, a departing engineer's key gets removed from three servers but survives on the fourth, and everyone types "yes" on first connection without ever checking a fingerprint. The keys are not the problem. The problem is that the mapping between keys and people lives nowhere central. OpenSSH has shipped a built-in answer for years: a certificate authority. With no extra server and no extra software you can issue short-lived credentials that say exactly who they belong to and which accounts they open.

Three approaches and where each one fits

Distributed authorized_keys. A configuration management tool writes keys to every server. For five servers and three people it is perfectly fine. At scale two weaknesses show up: revocation only happens once the new file reaches every host, and a key never expires. A private key copied off a laptop two years ago still works today.

Central lookup with AuthorizedKeysCommand. sshd runs a command on every login and fetches keys from a directory service. Revocation is instant, but logging in now depends on that service being up. When the directory is slow or the network is partitioned you cannot get onto your servers, which is usually exactly when you need to.

Certificate authority. A CA key signs the user's public key, and servers trust only the CA's public key. The certificate carries an identity, the Unix accounts it may log in as, and a validity window. The server asks nobody at login time, so there is no runtime dependency. Expiry handles revocation: if a certificate lives for eight hours, you often do not need a revocation list at all.

If you want a verdict: once you are past roughly ten servers or more than one team, certificates are clearly the right choice. You get the revocation speed of a central lookup through short lifetimes, without making every login depend on a live service.

User certificates: the basic setup

Generate the CA key somewhere separate from your servers and well protected:

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

Sign a user's public key:

ssh-keygen -s user_ca -I "alice@example" -n alice,deploy -V -5m:+8h -z 1001 id_ed25519.pub

This writes id_ed25519-cert.pub next to the key. The flags matter:

  • -I is the key identity, and it is what shows up in the server log. Use something that names the person and the device, because that is what you will search for in audit trails.
  • -n is the principal list, the names this certificate is accepted for.
  • -V -5m:+8h starts validity five minutes in the past and ends it eight hours from now. The backdated start is a safety margin for servers with clock drift.
  • -z sets a serial number, which you need for revocation.

Always inspect what you signed:

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

On the server, in sshd_config:

TrustedUserCAKeys /etc/ssh/user_ca.pub
AuthorizedPrincipalsFile /etc/ssh/auth_principals/%u

If /etc/ssh/auth_principals/deploy contains the line deploy, any certificate listing deploy as a principal may log in as that account. Without AuthorizedPrincipalsFile the default rule is that a principal must match the Unix username exactly. Designing principals as roles rather than people (for example web-operator or db-readonly) lets you grant access without touching server files: authorization is decided at signing time.

You can also attach restrictions when signing. For a CI job's key:

ssh-keygen -s user_ca -I "ci-deploy" -n deploy -V +30m \
  -O no-port-forwarding -O no-pty -O source-address=203.0.113.0/24 ci_key.pub

Host certificates: ending the "yes" habit

The same mechanism works in the other direction, and it is the half most setups forget. Sign the server's host key with a separate host CA:

ssh-keygen -s host_ca -I "web01" -h -n web01.example.com,web01 -V +52w \
  /etc/ssh/ssh_host_ed25519_key.pub

-h marks it as a host certificate. Point sshd at it:

HostCertificate /etc/ssh/ssh_host_ed25519_key-cert.pub

Clients need one line in known_hosts:

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

From now on nobody approves a fingerprint when a new server comes up. A valid signature connects, an invalid one triggers a warning. Protection against man-in-the-middle attacks stops depending on individual discipline.

Pitfalls

One CA for both users and hosts. If a single key does both jobs, any automation allowed to sign host keys can effectively mint user certificates too. Keep two CAs with separate access.

Leaving out principals. How a certificate signed without -n is interpreted has varied across OpenSSH versions and server settings. Do not rely on it. Always pass an explicit principal list, and make your signing tooling refuse an empty one.

Old authorized_keys left in place. If you moved to certificates but the old keys still sit on every server, you are exactly as exposed as before, just with more moving parts. Once migration is done, close that path with AuthorizedKeysFile none. Before you reload sshd, keep a second session open that you know can log in with a certificate.

Clock skew. Validity is checked against the server's clock. A server running a few minutes fast rejects a freshly signed certificate as "not yet valid". Backdate the start and monitor time sync on your hosts.

RSA CAs and SHA-1. Certificates signed by an RSA CA with SHA-1 are rejected by default in recent OpenSSH releases. Use ed25519 for new CAs; if you must use RSA, pick a SHA-2 signature algorithm.

Long-lived certificates. Issue a one-year user certificate "because it's needed now and then" and you are back to a key that never expires, with the revocation problem that comes with it. If you really need long lifetimes, set up a revocation list:

ssh-keygen -k -f /etc/ssh/revoked_keys -s user_ca.pub -z 1 revoke.txt

revoke.txt contains lines such as serial: 1001, and sshd enforces the list with RevokedKeys /etc/ssh/revoked_keys. That file has to reach every server, so the distribution problem returns. Short lifetimes remain the first line of defense.

The client not finding the certificate. A certificate is picked up automatically when it sits next to the key with a -cert.pub suffix. If you use another name or location, set CertificateFile in ssh_config. If you use an agent, re-add the key after renewing the certificate, otherwise the agent keeps presenting the old one.

Who does the signing

Putting the CA key on someone's laptop defeats the whole design. In practice signing sits behind a system that already authenticates people: the user logs in through single sign-on, the signing service derives principals from identity and group membership, and it returns a certificate valid for a few hours. HashiCorp Vault's SSH secrets engine and step-ca do this out of the box. For a small team a simple script on a locked-down machine is a fine start. What matters is that the CA key is never distributed and every signature is logged.

When not to choose it

With one or two servers and a single administrator, a certificate setup creates more work than it saves; well protected keys and a bastion with two-factor login are enough. If you already run an access tool that provides session recording, approval flows and access requests, use its certificate support instead of building the same thing by hand. And if you cannot automate signing, do not start: hand-signed certificates soon get issued with long lifetimes, and the whole advantage disappears.

Done right, the only access material stored on servers is a CA public key, every login appears in the log under a person's name, and a departing engineer's access ends at the latest when their certificate expires.