Issue short-lived SSH certificates with OpenSSH CA instead of static keys
For developers and platform engineers replacing long-lived authorized_keys sprawl with OpenSSH user certificates. You’ll set up a small SSH CA, trust it on servers, issue a short-lived cert for a user key, and verify certificate-based SSH login end to end.
TL;DR — Replace per-server static public keys with a single trusted SSH CA on your servers, then sign users’ existing public keys with short expirations like 8h. The most common fix when this fails is on the server side: add the CA public key to
TrustedUserCAKeysinsshd_config, reloadsshd, and verify the cert principal matchesAuthorizedPrincipalsFile. Reading time: ~5 min
Goal
When you finish, your target Linux server accepts SSH login using a short-lived OpenSSH user certificate signed by your CA, and the same user key no longer needs to be copied into each server’s authorized_keys.
Prerequisites
- Root or sudo access on at least one target Linux server running OpenSSH server — check with
sshd -Vorssh -V(OpenSSH 8.4+ recommended; newer is better) - A workstation with OpenSSH client tools — check with
ssh -Vandssh-keygen -h | head - One existing user account on the server to log in as, for example
deployor your own Unix username - A secure place to store the SSH CA private key, ideally an offline/admin host; do not generate the CA key on an app server
- The user public key you want to certify, for example
~/.ssh/id_ed25519.pub; create one if needed - The list of allowed certificate principals you want to map to server accounts, for example
alice,deploy, orprod-admin
Steps
Step 1: Create an SSH user CA keypair
⚠️ The CA private key can sign access for every server that trusts it. Store it on an admin host with restricted access, not on the target server.
Run this on your admin host:
mkdir -p ~/ssh-ca
chmod 700 ~/ssh-ca
ssh-keygen -t ed25519 -f ~/ssh-ca/user_ca -C "ssh-user-ca" -N ""
ls -l ~/ssh-ca/user_ca*
You should see ~/ssh-ca/user_ca and ~/ssh-ca/user_ca.pub, with the private key mode 600.
Step 2: Configure the server to trust your CA
Copy the CA public key to the server and point sshd at it:
scp ~/ssh-ca/user_ca.pub root@server.example.com:/etc/ssh/user_ca.pub
ssh root@server.example.com 'chmod 644 /etc/ssh/user_ca.pub && grep -q "^TrustedUserCAKeys /etc/ssh/user_ca.pub$" /etc/ssh/sshd_config || printf "\nTrustedUserCAKeys /etc/ssh/user_ca.pub\n" >> /etc/ssh/sshd_config && sshd -t && systemctl reload sshd'
You should get your shell prompt back with no output; if sshd -t fails, the command exits non-zero and prints the config error.
Step 3: Restrict which principals can log in as the target account
On the server, create an AuthorizedPrincipalsFile and bind it to the Unix account. This prevents any cert signed by your CA from becoming universal access.
For a deploy account that should accept the principal deploy:
ssh root@server.example.com '
install -d -m 755 /etc/ssh/auth_principals &&
printf "deploy\n" > /etc/ssh/auth_principals/deploy &&
chmod 644 /etc/ssh/auth_principals/deploy &&
grep -q "^AuthorizedPrincipalsFile /etc/ssh/auth_principals/%u$" /etc/ssh/sshd_config || printf "AuthorizedPrincipalsFile /etc/ssh/auth_principals/%%u\n" >> /etc/ssh/sshd_config &&
sshd -t && systemctl reload sshd
'
You should get no output and exit status 0.
Step 4: Create or reuse the user key that will receive the certificate
If you already have a user key, skip the first command and use its .pub file in the next step.
test -f ~/.ssh/id_ed25519 || ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519 -C "$USER@$(hostname)-userkey" -N ""
ls -l ~/.ssh/id_ed25519 ~/.ssh/id_ed25519.pub
You should see the private key and public key in ~/.ssh/.
Step 5: Sign the user public key with a short lifetime
Sign the user public key with your CA. This example issues an 8-hour cert for principal deploy and key ID alice-20261030.
ssh-keygen -s ~/ssh-ca/user_ca -I alice-20261030 -n deploy -V +8h ~/.ssh/id_ed25519.pub
ssh-keygen -L -f ~/.ssh/id_ed25519-cert.pub
You should see output shaped like this:
Signed user key ~/.ssh/id_ed25519-cert.pub: id "alice-20261030" serial 0 for deploy valid from 2026-10-30T09:12:00 to 2026-10-30T17:13:02
~/.ssh/id_ed25519-cert.pub:
Type: ssh-ed25519-cert-v01@openssh.com user certificate
Public key: ED25519-CERT SHA256:...
Signing CA: ED25519 SHA256:... (using ssh-ed25519)
Key ID: "alice-20261030"
Serial: 0
Valid: from 2026-10-30T09:12:00 to 2026-10-30T17:13:02
Principals:
deploy
Step 6: Use the certificate for SSH
OpenSSH automatically pairs id_ed25519 with id_ed25519-cert.pub if both are in ~/.ssh/. To be explicit during rollout, pass both files:
ssh -i ~/.ssh/id_ed25519 -o CertificateFile=~/.ssh/id_ed25519-cert.pub deploy@server.example.com
You should land on the server as deploy without having added your public key to ~deploy/.ssh/authorized_keys.
Step 7: Add a client config entry so users stop thinking about flags
Put this in ~/.ssh/config on the workstation:
Host server-cert
HostName server.example.com
User deploy
IdentityFile ~/.ssh/id_ed25519
CertificateFile ~/.ssh/id_ed25519-cert.pub
IdentitiesOnly yes
Then connect with:
ssh server-cert
You should connect with no extra flags.
View as text
[Admin Host] ---- 1. sign user key ----> [User Cert]
|
v
[SSH CA pubkey] ---- 2. trust CA ----> [Server sshd]
[Developer] ---- 3. ssh with key+cert ----> [Server sshd]Verify it works
Run a verbose SSH connection and confirm the server accepts the certificate, not a static authorized key:
ssh -vvv -i ~/.ssh/id_ed25519 -o CertificateFile=~/.ssh/id_ed25519-cert.pub deploy@server.example.com true
Expected lines include output shaped like this:
debug1: Offering public key: /home/alice/.ssh/id_ed25519 ED25519 SHA256:...
debug1: Offering public key: /home/alice/.ssh/id_ed25519-cert.pub ED25519-CERT SHA256:...
debug1: Server accepts key: /home/alice/.ssh/id_ed25519-cert.pub ED25519-CERT SHA256:...
Authenticated to server.example.com ([203.0.113.10]:22) using "publickey".
Also verify the target account does not rely on your static key:
ssh root@server.example.com 'test ! -f ~deploy/.ssh/authorized_keys || ! grep -q "$(cut -d" " -f2 < ~/.ssh/id_ed25519.pub 2>/dev/null)" ~deploy/.ssh/authorized_keys; echo $?'
Expected result: 0 means your local public key is not present in ~deploy/.ssh/authorized_keys.
Common pitfalls
TrustedUserCAKeys points to the wrong file
Mistake: you copied the CA private key path or a user public key instead of the CA public key.
Symptom: ssh -vvv shows Permission denied (publickey) and the server logs contain Certificate invalid: untrusted CA.
Fix: set TrustedUserCAKeys /etc/ssh/user_ca.pub, then run sshd -t && systemctl reload sshd.
The certificate principal does not match the server account
Mistake: you signed with -n alice but are logging in as deploy, while /etc/ssh/auth_principals/deploy contains only deploy.
Symptom: client sees Permission denied (publickey); server logs show Certificate does not contain an authorized principal.
Fix: re-sign with the right principal, for example ssh-keygen -s ~/ssh-ca/user_ca -I alice-20261030 -n deploy -V +8h ~/.ssh/id_ed25519.pub.
The certificate expired or the server clock is wrong
Mistake: you used a very short validity window or the server time is skewed.
Symptom: server logs show Certificate invalid: expired or not yet valid; ssh-keygen -L -f ~/.ssh/id_ed25519-cert.pub shows a window that should work.
Fix: sync time on the server with NTP/chrony and reissue with a practical window like -V +8h.
sshd was edited but not reloaded
Mistake: you changed sshd_config or AuthorizedPrincipalsFile and tested immediately.
Symptom: the old behavior persists; ssh -vvv still fails exactly as before.
Fix: run sshd -t && systemctl reload sshd on the server after every config change.
You still have a matching static key, so your test is misleading
Mistake: your public key is already in authorized_keys, so login succeeds even if cert trust is broken.
Symptom: SSH works, but ssh -vvv shows Server accepts key: ... id_ed25519 and never accepts id_ed25519-cert.pub.
Fix: temporarily remove the matching line from authorized_keys for the test or force the cert path with IdentitiesOnly yes and inspect -vvv output.
File permissions block the principal file or SSH config
Mistake: principal files or ~/.ssh/config have odd ownership or modes.
Symptom: server-side auth fails silently, or client prints Bad owner or permissions on /home/alice/.ssh/config.
Fix: run chmod 600 ~/.ssh/config, chmod 644 /etc/ssh/auth_principals/deploy, and keep /etc/ssh/auth_principals owned by root.
This article was written by an AI system and published pending human review. Verify anything you intend to act on.
Have a project in mind?
Get an instant AI price estimate for it, or talk directly to our team.
One email a month on what we learn building with AI