authorized_keys
# Generate the User CA key (keep this extremely safe)
ssh-keygen -t ed25519 -f user_ca -C "user-ca@yourorg" # Generate the Host CA key
ssh-keygen -t ed25519 -f host_ca -C "host-ca@yourorg"
# Generate the User CA key (keep this extremely safe)
ssh-keygen -t ed25519 -f user_ca -C "user-ca@yourorg" # Generate the Host CA key
ssh-keygen -t ed25519 -f host_ca -C "host-ca@yourorg"
# Generate the User CA key (keep this extremely safe)
ssh-keygen -t ed25519 -f user_ca -C "user-ca@yourorg" # Generate the Host CA key
ssh-keygen -t ed25519 -f host_ca -C "host-ca@yourorg"
# Sign the server's host key with your Host CA
# -s: signing key, -I: certificate identity (for logging)
# -h: this is a HOST certificate (not user)
# -n: principals (hostnames this cert is valid for)
# -V: validity period
ssh-keygen -s host_ca \ -I "webserver-prod-01" \ -h \ -n "webserver-prod-01.example.com,10.0.1.50" \ -V +52w \ /etc/ssh/ssh_host_ed25519_key.pub
# Sign the server's host key with your Host CA
# -s: signing key, -I: certificate identity (for logging)
# -h: this is a HOST certificate (not user)
# -n: principals (hostnames this cert is valid for)
# -V: validity period
ssh-keygen -s host_ca \ -I "webserver-prod-01" \ -h \ -n "webserver-prod-01.example.com,10.0.1.50" \ -V +52w \ /etc/ssh/ssh_host_ed25519_key.pub
# Sign the server's host key with your Host CA
# -s: signing key, -I: certificate identity (for logging)
# -h: this is a HOST certificate (not user)
# -n: principals (hostnames this cert is valid for)
# -V: validity period
ssh-keygen -s host_ca \ -I "webserver-prod-01" \ -h \ -n "webserver-prod-01.example.com,10.0.1.50" \ -V +52w \ /etc/ssh/ssh_host_ed25519_key.pub
/etc/ssh/ssh_host_ed25519_key-cert.pub
/etc/ssh/sshd_config
# Present our signed host certificate to connecting clients
HostCertificate /etc/ssh/ssh_host_ed25519_key-cert.pub
# Present our signed host certificate to connecting clients
HostCertificate /etc/ssh/ssh_host_ed25519_key-cert.pub
# Present our signed host certificate to connecting clients
HostCertificate /etc/ssh/ssh_host_ed25519_key-cert.pub
known_hosts
# Add to ~/.ssh/known_hosts or /etc/ssh/ssh_known_hosts
# The @cert-authority directive tells SSH to trust any host
# certificate signed by this CA for matching hosts
@cert-authority *.example.com ssh-ed25519 AAAA...your-host-ca-public-key...
# Add to ~/.ssh/known_hosts or /etc/ssh/ssh_known_hosts
# The @cert-authority directive tells SSH to trust any host
# certificate signed by this CA for matching hosts
@cert-authority *.example.com ssh-ed25519 AAAA...your-host-ca-public-key...
# Add to ~/.ssh/known_hosts or /etc/ssh/ssh_known_hosts
# The @cert-authority directive tells SSH to trust any host
# certificate signed by this CA for matching hosts
@cert-authority *.example.com ssh-ed25519 AAAA...your-host-ca-public-key...
.example.com
known_hosts
# Sign a developer's public key with the User CA
# -n: principals (usernames they can log in as)
# -V: valid for 8 hours — short-lived certs are the sweet spot
ssh-keygen -s user_ca \ -I "alice@yourorg" \ -n "alice,deploy" \ -V +8h \ ~/.ssh/id_ed25519.pub
# Sign a developer's public key with the User CA
# -n: principals (usernames they can log in as)
# -V: valid for 8 hours — short-lived certs are the sweet spot
ssh-keygen -s user_ca \ -I "alice@yourorg" \ -n "alice,deploy" \ -V +8h \ ~/.ssh/id_ed25519.pub
# Sign a developer's public key with the User CA
# -n: principals (usernames they can log in as)
# -V: valid for 8 hours — short-lived certs are the sweet spot
ssh-keygen -s user_ca \ -I "alice@yourorg" \ -n "alice,deploy" \ -V +8h \ ~/.ssh/id_ed25519.pub
id_ed25519-cert.pub
/etc/ssh/sshd_config
# Trust any user certificate signed by our User CA
TrustedUserCAKeys /etc/ssh/user_ca.pub
# Trust any user certificate signed by our User CA
TrustedUserCAKeys /etc/ssh/user_ca.pub
# Trust any user certificate signed by our User CA
TrustedUserCAKeys /etc/ssh/user_ca.pub
authorized_keys
# Create or update the revoked keys file
ssh-keygen -k -f /etc/ssh/revoked_keys -s user_ca id_ed25519.pub
# Create or update the revoked keys file
ssh-keygen -k -f /etc/ssh/revoked_keys -s user_ca id_ed25519.pub
# Create or update the revoked keys file
ssh-keygen -k -f /etc/ssh/revoked_keys -s user_ca id_ed25519.pub
sshd_config
RevokedKeys /etc/ssh/revoked_keys
RevokedKeys /etc/ssh/revoked_keys
RevokedKeys /etc/ssh/revoked_keys
authorized_keys
valid after
AuthorizedPrincipalsFile
# In sshd_config, restrict which principals can log into this server
AuthorizedPrincipalsFile /etc/ssh/auth_principals/%u
# In sshd_config, restrict which principals can log into this server
AuthorizedPrincipalsFile /etc/ssh/auth_principals/%u
# In sshd_config, restrict which principals can log into this server
AuthorizedPrincipalsFile /etc/ssh/auth_principals/%u
/etc/ssh/auth_principals/deploy
ssh-keygen -L -f id_ed25519-cert.pub
ssh-keygen -L -f id_ed25519-cert.pub
ssh-keygen -L -f id_ed25519-cert.pub
authorized_keys
authorized_keys - Onboarding is manual. Every new team member's public key needs to land on every relevant server. That's N users × M servers worth of configuration.
- Offboarding is terrifying. Did you remove that key from every server? Are you sure? What about that one bastion host nobody remembers setting up in 2021?
- Host verification is a joke. Be honest — when was the last time you actually verified an SSH host fingerprint? You type yes and move on like the rest of us.
- No expiration. SSH keys live forever by default. That contractor who helped out for two weeks three years ago? Their key might still be valid. - The CA signs user and host public keys, producing certificates.
- Servers trust the CA, not individual user keys.
- Clients trust the CA, not individual host fingerprints. - Principals matter. If you sign a user cert with -n alice but the user tries to log in as deploy, it fails. Think carefully about which principals each person needs.
- Clock skew kills you. Certificates have validity windows. If your server's clock is off by a few minutes and the cert's valid after timestamp is in the server's future, authentication fails silently. Use NTP. Seriously.
- Don't lose the CA key. If you lose the CA private key, you need to redistribute trust from scratch. If someone else gets it, they can sign certificates for anyone. Protect it accordingly.
- AuthorizedPrincipalsFile is your friend. Without it, any user certificate signed by the CA can log in as any principal. You almost certainly want to restrict this per-server. - Automate cert signing. Don't make developers email you their public keys. Build or use a signing service. Projects like step-ca from Smallstep or HashiCorp Vault's SSH secrets engine handle this well.
- Keep cert lifetimes short. 8-24 hours for user certs. This dramatically reduces the risk window if a key is compromised.
- Use separate CAs for users and hosts. I mentioned this earlier but it bears repeating.
- Automate host cert renewal. Host certs with a 52-week lifetime should get renewed automatically, not when someone notices SSH connections failing.
- Log the certificate IDs. The -I identity string shows up in auth logs. Use meaningful identifiers so you can trace access.