SSH Certificate Authorities: Access Control That Scales

SSH Certificate Authorities: Access Control That Scales

authorized_keys does not scale. Fifty servers and twenty people means managing a thousand key entries, and removing someone who leaves means finding every file that has their key.

SSH certificates fix this. Our SSH keys guide covers the basics; this is what comes after.

The model

A certificate authority is an SSH keypair whose only job is signing other keys.

A server is told “trust this CA.” Anyone presenting a certificate signed by that CA is allowed in, according to what the certificate says. No per-user configuration on the server at all.

Granting access means signing a certificate. Revoking it means not signing another one.

Setting up

# on an offline or well-protected machine
ssh-keygen -t ed25519 -f user_ca -C "user CA"
ssh-keygen -t ed25519 -f host_ca -C "host CA"

Two separate CAs. One for signing user keys, one for signing host keys. Mixing them means a compromise of either is a compromise of both.

The private keys are the most valuable secrets you now have. Anyone holding user_ca can mint a certificate for any user on every server that trusts it. Keep them offline, on a hardware token, or in a proper secrets system, and never on a server that accepts logins.

User certificates

Tell the server to trust the CA:

# /etc/ssh/sshd_config
TrustedUserCAKeys /etc/ssh/user_ca.pub
sudo cp user_ca.pub /etc/ssh/
sudo systemctl reload sshd

Sign a user’s key:

ssh-keygen -s user_ca \
  -I "colton@example.com" \
  -n colton,deploy \
  -V +8h \
  -z 1001 \
  colton_id_ed25519.pub

Each flag matters:

-I is the key identity, which appears in the server’s auth log. This is your audit trail, so make it meaningful.

-n lists principals, the usernames this certificate may log in as. -n colton,deploy means it works for both.

-V +8h sets validity. This is the revocation mechanism. A certificate valid for a working day expires on its own, which avoids the distribution problem that makes revocation lists painful.

-z is a serial number, needed if you later want to revoke a specific certificate.

The result is colton_id_ed25519-cert.pub, which sits next to the private key. SSH presents it automatically.

ssh-keygen -L -f colton_id_ed25519-cert.pub    # inspect it
ssh deploy@server

Host certificates, the underrated half

Every SSH user has typed yes to an unknown host fingerprint without checking it. That prompt is the entire defence against a machine-in-the-middle attack, and essentially nobody verifies it.

Host certificates remove the prompt for legitimate hosts, which makes it meaningful when it does appear.

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

-h marks it a host certificate. Principals are the names and addresses clients use to reach it.

# /etc/ssh/sshd_config
HostCertificate /etc/ssh/ssh_host_ed25519_key-cert.pub

On the client:

# ~/.ssh/known_hosts, or /etc/ssh/ssh_known_hosts
@cert-authority *.example.com ssh-ed25519 AAAAC3Nza...

One line, and every host in the domain is trusted without a per-host entry. New servers work immediately; a server presenting a key without a valid certificate prompts, which is now a signal worth reading.

This also fixes the rebuilt-server problem. Our Clonezilla guide notes that cloned machines share host keys; with certificates, regenerating a host key and signing it is routine rather than a wave of warnings.

Restrictions in the certificate

Certificates carry options, which is more granular than authorized_keys and cannot be edited on the server:

ssh-keygen -s user_ca \
  -I "ci-deploy" \
  -n deploy \
  -V +1h \
  -O no-agent-forwarding \
  -O no-port-forwarding \
  -O no-pty \
  -O source-address=203.0.113.0/24 \
  -O force-command="/usr/local/bin/deploy.sh" \
  ci_key.pub

A CI deployment certificate that works for one hour, only from your build network, cannot forward anything, gets no interactive shell, and runs one command.

no-agent-forwarding is worth defaulting to, for the reasons in our ssh-agent guide: a forwarded agent on a host you do not fully control lets anyone with root there authenticate as you.

Revocation before expiry

Short lifetimes handle most cases. For immediate revocation:

ssh-keygen -k -f revoked_keys -z 1 colton_id_ed25519-cert.pub
ssh-keygen -k -u -f revoked_keys -z 2 other-cert.pub    # append
# /etc/ssh/sshd_config
RevokedKeys /etc/ssh/revoked_keys

The KRL must be distributed to every server, which is the problem certificates were meant to avoid. Treat it as the emergency path and rely on short validity for normal turnover.

Issuing at scale

Signing by hand does not scale either. The usual approach is a service that authenticates against your existing identity provider and issues a short-lived certificate.

HashiCorp Vault has an SSH secrets engine. Smallstep offers step-ca, which is open source and well documented. Teleport is a heavier option that adds session recording and access control.

The shape is the same: log in with SSO, receive an eight-hour certificate, work normally. Revoking someone is disabling their SSO account, and their access ends when the current certificate expires.

Migrating

Certificates and authorized_keys coexist, so do it gradually.

  1. Create the CAs and protect the private keys.
  2. Add TrustedUserCAKeys to servers. Existing key auth keeps working.
  3. Issue certificates to a few people and confirm.
  4. Add host certificates and @cert-authority on clients.
  5. Remove authorized_keys entries once everything works.

Keep one break-glass key on each server, stored offline, until you are confident. A CA misconfiguration that locks you out of every machine simultaneously is the failure mode to plan for, and it is exactly the kind of thing our SSH hardening guide warns about with its advice to keep a second session open while testing.

# verify before you rely on it
ssh -v deploy@server 2>&1 | grep -i cert
sudo journalctl -u sshd | grep "Accepted publickey.*ID"

That last command shows the certificate identity in the auth log, which is the audit trail authorized_keys never gave you.

Frequently Asked Questions

What problem do SSH certificates solve?

They remove the need to distribute public keys to every server. A server trusts one certificate authority, and anyone holding a valid certificate signed by it can log in, so granting and revoking access happens at the CA rather than by editing authorized_keys on every host.

How do SSH certificates handle revocation?

Primarily through short lifetimes. A certificate valid for eight hours revokes itself by expiring, which avoids the distribution problem that plagues revocation lists. For immediate revocation before expiry, a KRL file can be published and referenced by sshd.

Do SSH certificates also fix host key verification?

Yes, and this is the underrated half. Signing host keys with a host CA means clients trust any host presenting a valid certificate, so the unknown host fingerprint prompt disappears for legitimate servers and reappears meaningfully for ones that are not.

What is the difference between a user certificate and a host certificate?

A user certificate proves a user is authorised and is presented to the server at login. A host certificate proves a server is what it claims and is presented to the client. They are signed with the -n flag for principals and the -h flag for host certificates respectively.

Where should the CA private key live?

Offline or in hardware, never on a server that accepts logins. Anyone with the CA key can mint a certificate for any user on every host that trusts it, which makes it the single most valuable secret in the setup. A hardware token or an air-gapped machine is appropriate.

Can I use SSH certificates alongside authorized_keys?

Yes, and that is the sensible migration path. A server can trust a CA and still honour existing authorized_keys entries, so you roll certificates out gradually and remove the key files once everything works. Keep one break-glass key until you are confident.