Skip to content

SSH certificates instead of authorized_keys

authorized_keys works fine for a handful of servers. Once you hit a dozen, it becomes a liability. Onboarding a new developer means manually distributing their public key across every machine. SSH certificates flip this model: one CA signs all public keys, and authorized_keys stays empty.

Why authorized_keys breaks at scale

authorized_keys requires your public key to exist on every target server. Scaling this creates:

  • a separate deployment step for key distribution during onboarding
  • no centralized revocation — removing a key means touching each host
  • key rotation touches every machine
  • no expiration means stale access accumulates

An SSH certificate is a CA signature on your public key. The server only needs to trust the CA — your key never needs to be present locally.

How SSH certificates work

Two entities: the CA (Certificate Authority) and the signed key. The CA is a standard SSH keypair, typically ed25519 or RSA. Signing creates the certificate with ssh-keygen -s <ca_private> -I <identifier> <key.pub>, producing <key-cert.pub>.

The server needs only two things: the CA public key in TrustedUserCAKeys (for users) or a HostCertificate directive (for hosts). Authentication succeeds if the signature is valid and the certificate hasn’t expired.

Note

A certificate does not replace the key. The key is still required — the CA signs it. The certificate adds metadata: TTL, principals, extensions, serial number.

Generating CA keys

ssh-keygen -t ed25519 -f /etc/ssh/ca_user -C "CA for user certificates"
ssh-keygen -t ed25519 -f /etc/ssh/ca_host -C "CA for host certificates"
FlagPurpose
-t ed25519Key type; ed25519 recommended per RFC 8709
-fOutput file path
-CComment; use for CA identification

Store CA private keys securely — ideally on a dedicated build machine or in an HSM. The CA private key never goes to target servers.

Signing user certificates: one-liner

ssh-keygen -s /etc/ssh/ca_user \
  -I "john@devops" \
  -n ubuntu,deploy \
  -V +52w \
  -z 1 \
  ~/.ssh/id_ed25519.pub
FlagPurpose
-s ca_privateCA private key for signing
-I identifierString logged during authentication
-n principalsComma-separated list of authorized identities
-V +52wValidity: 52 weeks from now
-z serialSerial number; useful for audit trails

The result is id_ed25519-cert.pub next to your private key. The user keeps both files. Signing another employee’s key takes the same command with a different key.

Tip

Duration suffixes: h (hours), d (days), w (weeks). +1d is one day, -1d means yesterday — already expired.

Signing host certificates

Generate host keys on each server if they don’t exist:

ssh-keygen -t ed25519 -f /etc/ssh/ssh_host_ed25519_key -N ""

Sign on the CA machine:

ssh-keygen -s /etc/ssh/ca_host \
  -I "prod-web-01" \
  -h \
  -n prod-web-01,10.0.1.5 \
  -V +52w \
  /etc/ssh/ssh_host_ed25519_key.pub

The -h flag marks this as a host certificate. The -n value lists the hostname and IP the client will verify on connect.

Place the certificate next to the host key:

cp /tmp/ssh_host_ed25519_key-cert.pub /etc/ssh/ssh_host_ed25519_key-cert.pub

sshd_config: CertFile, TrustedUserCAKeys, HostCertificate

Configuration on the target server:

# /etc/ssh/sshd_config.d/certs.conf

# Enable certificate authentication
PubkeyAuthentication yes

# CA public key trusted for user authentication
TrustedUserCAKeys /etc/ssh/ca_user.pub

# Host certificate path
HostCertificate /etc/ssh/ssh_host_ed25519_key-cert.pub

# Optional: per-user principal files
# AuthorizedPrincipalsFile /etc/ssh/%u.principals

Validate and reload after changes:

sshd -t && systemctl reload sshd
Warning

TrustedUserCAKeys expects the CA public key, not a certificate. Certificates are only needed for host keys.

Principals and source restrictions

You can restrict a certificate to specific source IPs at signing time:

ssh-keygen -s /etc/ssh/ca_user \
  -I "jenkins@ci" \
  -n deploy \
  -O source-address=10.8.0.0/16 \
  ~/.ssh/id_ed25519.pub

Or enforce from via AuthorizedPrincipalsFile on the server:

# /etc/ssh/deploy.principals
deploy from="10.8.0.0/16"

A certificate can carry multiple principals. sshd checks if any principal matches those listed in AuthorizedPrincipalsFile. This lets you issue a cert with ubuntu,deploy,admin and grant access through different principals on different hosts.

Time-to-live and rotation

Set TTL at signing. Practical guidelines:

RoleRecommended TTLRationale
CI/CD, automation24–72 hoursPipeline credentials are short-lived
Developers1–6 monthsBalance between security and convenience
Hosts12 monthsHost certificates bind to hostname/IP

Rotation means signing a new certificate with a fresh serial. The old one becomes invalid after expiry. No centralized revocation list needed — an expired certificate simply fails validation.

Fingerprints: the host-key problem

Traditional known_hosts stores host key fingerprints. Host certificates break this: the fingerprint in known_hosts won’t match the certificate. Two solutions:

Use ssh_known_hosts with ssh-keyscan:

ssh-keyscan -t ed25519 prod-web-01 >> /etc/ssh/ssh_known_hosts

Or explicitly prefer certificate algorithms in your client config:

Host prod-web-01
    HostKeyAlgorithms ssh-ed25519-cert-v01@openssh.com
    UpdateHostKeys ask

On first connect, ssh will prompt to accept the certificate, add it to known_hosts, and won’t ask again.

Troubleshooting

Debug in order:

# Check sshd accepted the config
sshd -t

# Watch authentication logs
journalctl -u sshd -f

# Verbose client output
ssh -vvv user@host

In -vvv output, look for Certificate lines and Authentications that can continue. no matching identity means the certificate isn’t next to the key. certificate refused indicates the CA isn’t trusted or the principal didn’t match.

Inspect certificate contents:

ssh-keygen -Lf ~/.ssh/id_ed25519-cert.pub

Output shows Valid: from ... to ..., Principals:, Serial:. First place to check when something breaks.

Note

too many authentication failures with a valid certificate usually means ssh is cycling through all keys before reaching the cert. Add -o PubkeyAuthentication=no before explicit key specification, or remove extraneous keys from .ssh.

SSH certificates eliminate key distribution across hosts. One CA, signed keys with TTL, principals for access control. If you manage more than five servers, this isn’t optional — it’s infrastructure.