If you manage more than a handful of Linux servers, authorized_keys eventually becomes a mess:
- keys copied everywhere
- stale access that never gets cleaned up
- painful offboarding
- no easy way to force short-lived access
OpenSSH has a built-in answer: user certificates signed by your own SSH Certificate Authority (CA).
Instead of distributing every user key to every server, you:
- trust one CA public key on servers,
- issue short-lived user certificates,
- control access with principals,
- revoke when needed.
This guide is hands-on and keeps the moving parts minimal.
Why SSH certificates are cleaner than authorized_keys
With classic public-key auth, each server must store each user key (or fetch it dynamically). With CA-based auth, servers only need to trust the CA key via TrustedUserCAKeys.
From there, login is allowed when:
- the cert is valid (
-Vwindow), - cert principal matches what server accepts,
- cert is signed by trusted CA.
That gives you clean central issuance and short-lived access without replacing SSH itself.
Lab topology used in this tutorial
- CA host (secure admin machine): signs user keys
- Target server: trusts CA pubkey and enforces principals
- User laptop: has user key + signed cert
All commands below are Linux/OpenSSH-native.
Step 1) Create a dedicated SSH user CA key
Do this once, store the private key securely, and back it up safely.
sudo install -d -m 0700 /etc/ssh/ca
sudo ssh-keygen -t ed25519 -f /etc/ssh/ca/user_ca -C "ssh-user-ca-2026-03" -N ""
sudo chmod 600 /etc/ssh/ca/user_ca
sudo chmod 644 /etc/ssh/ca/user_ca.pub
You will distribute only user_ca.pub to servers.
Step 2) Configure server trust + principal mapping
On each target server:
sudo install -d -m 0755 /etc/ssh/auth_principals
sudo install -m 0644 /path/to/user_ca.pub /etc/ssh/trusted_user_ca_keys.pub
# Map Linux user "deploy" to allowed cert principals
printf 'deploy\nops\n' | sudo tee /etc/ssh/auth_principals/deploy >/dev/null
sudo chmod 0644 /etc/ssh/auth_principals/deploy
Now update /etc/ssh/sshd_config (or a drop-in under /etc/ssh/sshd_config.d/):
PubkeyAuthentication yes
TrustedUserCAKeys /etc/ssh/trusted_user_ca_keys.pub
AuthorizedPrincipalsFile /etc/ssh/auth_principals/%u
PasswordAuthentication no
Validate config and reload:
sudo sshd -t
sudo systemctl reload ssh
# On some distros: sudo systemctl reload sshd
Step 3) Create a user key and sign a short-lived certificate
On the user machine (or where user key is generated):
ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519 -C "[email protected]" -N ""
On the CA host, sign that public key for specific principals and a short validity window:
ssh-keygen \
-s /etc/ssh/ca/user_ca \
-I "ali-ticket-4821" \
-n deploy,ops \
-V +8h \
-z 1001 \
~/.ssh/id_ed25519.pub
This creates ~/.ssh/id_ed25519-cert.pub.
What those flags do:
-
-s: CA private key used to sign -
-I: key identity string (audit-friendly) -
-n: certificate principals (who/roles this cert can act as) -
-V: validity period (+8hhere) -
-z: serial number for tracking/revocation
Inspect the certificate:
ssh-keygen -L -f ~/.ssh/id_ed25519-cert.pub
Step 4) Connect using key + certificate
SSH automatically uses *-cert.pub when paired with the private key, but explicit config is clearer:
Host prod-web-01
HostName 203.0.113.10
User deploy
IdentityFile ~/.ssh/id_ed25519
CertificateFile ~/.ssh/id_ed25519-cert.pub
IdentitiesOnly yes
Connect:
ssh prod-web-01
If cert principal, validity, and server policy align, login succeeds with no per-host authorized_keys entry for that user key.
Step 5) Revoke certificates when needed (KRL)
If a cert or key should be blocked before expiry, use an OpenSSH KRL (Key Revocation List).
Create initial KRL:
sudo ssh-keygen -k -f /etc/ssh/revoked_keys.krl
sudo chmod 644 /etc/ssh/revoked_keys.krl
Add a certificate to revocation list:
sudo ssh-keygen -k -u -f /etc/ssh/revoked_keys.krl ~/.ssh/id_ed25519-cert.pub
Tell sshd to enforce it (/etc/ssh/sshd_config):
RevokedKeys /etc/ssh/revoked_keys.krl
Then reload:
sudo sshd -t
sudo systemctl reload ssh
Audit KRL contents:
ssh-keygen -Q -l -f /etc/ssh/revoked_keys.krl
Operational pattern that works in real teams
A practical baseline:
- CA key is offline or tightly restricted
- cert TTL: 4h–24h for humans, slightly longer for automation if needed
- principals represent roles (
ops,db-admin,deploy) not people - serials and
-Iidentity map to ticket/change IDs - KRL distributed to servers via config management
This gives you fast offboarding and much cleaner audit trails than scattered authorized_keys files.
Troubleshooting checklist
If login fails:
- Check server config syntax:
sudo sshd -t
- Confirm cert details:
ssh-keygen -L -f ~/.ssh/id_ed25519-cert.pub
- Verify principal is allowed for target user:
- cert principal appears in
/etc/ssh/auth_principals/<user>
- cert principal appears in
- Check validity window (
Valid:field fromssh-keygen -L) - Increase SSH client verbosity:
ssh -vvv deploy@server
- Check server logs (
journalctl -u ssh -u sshd -n 100)
Final thought
You don’t need a heavyweight access platform to stop key sprawl. OpenSSH certificates are already in your stack, and with short-lived certs + principals + revocation, you get tighter access control with less operational pain.
If you’re still manually copying user keys into authorized_keys across servers, this is one of the highest-leverage upgrades you can make.
Sources and references
- OpenSSH
ssh-keygen(1)manual (cert signing, validity, serials, KRL): https://man.openbsd.org/ssh-keygen.1 - OpenSSH
sshd_config(5)manual (TrustedUserCAKeys,AuthorizedPrincipalsFile,RevokedKeys): https://man.openbsd.org/sshd_config - Linux man-pages mirror for
sshd_config(5)(distribution-friendly reference): https://man7.org/linux/man-pages/man5/sshd_config.5.html - DEV API docs (publishing endpoint and payload shape): https://developers.forem.com/api
Top comments (0)