SSH Certificate Authorities (CAs) let you grant SSH access to your servers without managing individual public keys on each machine. Instead, you sign user or host public keys with your CA’s private key, creating short-lived, trusted certificates.

Let’s see this in action. Imagine you have a CA private key (ca.key) and its corresponding public key (ca.pub).

# On a user's machine, they generate a key pair
ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519

# The user sends their *public* key to the CA administrator
# Let's assume this is ~/.ssh/id_ed25519.pub

# The CA administrator signs the user's public key
# This command creates a certificate file: id_ed25519-cert.pub
ssh-keygen -s ca.key -I user_id@example.com -n user,otheruser -V +1d ~/.ssh/id_ed25519.pub

Here’s what happened:

  • -s ca.key: Specifies the CA private key to use for signing.
  • -I user_id@example.com: Sets an identity string for the certificate, often a username or email. This is logged on the server.
  • -n user,otheruser: Defines a comma-separated list of principals (usernames) the certificate is valid for on the target server. This is the key to impersonation.
  • -V +1d: Sets the validity period. +1d means valid for 1 day. Other options include 20231027:20231030 for specific dates or +1w for a week.
  • ~/.ssh/id_ed25519.pub: The user’s public key to be signed.

The output ~/.ssh/id_ed25519-cert.pub is the signed certificate. The user then places this certificate alongside their private key.

# User places the certificate in their SSH directory
cp id_ed25519-cert.pub ~/.ssh/

Now, when the user tries to connect to a server:

ssh user@your_server.example.com

The SSH client automatically looks for ~/.ssh/id_ed25519-cert.pub (or id_rsa-cert.pub, etc.) and presents it along with the private key. The server, configured to trust the CA, verifies the signature and checks the principals and validity period.

For this to work on the server, you need to tell it to trust your CA.

# On your_server.example.com, in sshd_config
TrustedUserCAKeys /etc/ssh/ca.pub

After adding this line, reload SSH: sudo systemctl reload sshd.

The TrustedUserCAKeys file on the server contains the public key of your CA. When an SSH connection comes in, sshd checks if the presented certificate was signed by a CA whose public key is in TrustedUserCAKeys. If it is, sshd then checks if the certificate’s principals (e.g., user or otheruser) match a local user account on the server. If both checks pass and the certificate is within its validity period, access is granted without needing the user’s password or a public key entry in authorized_keys.

This system solves the problem of managing thousands of authorized_keys files across hundreds of servers, especially in dynamic environments where users and servers come and go. Instead of distributing public keys, you distribute one CA public key to all your servers.

The magic lies in the -n (principals) flag during signing. When you sign userA.pub and specify -n admin,deploy, you’re essentially saying "this certificate, when presented with userA.pub, is valid for the local users named admin and deploy on the target machine." The server doesn’t care that the certificate was signed with userA.pub; it only cares that it was signed by a trusted CA and that the certificate itself grants permission for a specific principal that exists locally. This allows a single user’s key pair to gain access as multiple different users on different servers, all controlled by the CA’s signing policy.

The most surprising part is that the ssh-keygen command used to sign a certificate doesn’t actually need the private key of the user whose public key is being signed. It only needs the user’s public key. The CA’s private key is used to cryptographically bind the user’s public key to a set of permissions and a validity period. The SSH client and server then use the corresponding public keys (user’s public key and CA’s public key) to verify this binding.

The next step is to explore how to automate the issuance of these certificates, perhaps through a web interface or an internal API, and how to manage the lifecycle of the CA itself.

Want structured learning?

Take the full Ssh course →