Most home labs grow the same way. You add a server, copy your public key into ~/.ssh/authorized_keys, and move on. Two years later you have a dozen machines, a few laptops, and no reliable record of which keys can reach what. Removing a retired device means hunting through every server, and a leaked key stays valid until someone notices.
An SSH certificate authority (CA) fixes this. You keep one CA key pair. Every server trusts the CA’s public key, and every user and host receives a certificate signed by that CA. Each certificate carries an expiry date and a list of the names or accounts it is valid for. Servers stop tracking individual keys, and access ends on its own schedule.
This guide is for self-hosters and home-lab operators running OpenSSH on Linux. By the end, you will have a working CA that issues short-lived user certificates and host certificates, and you will have a plan for revoking access and renewing trust.
Before you start
- Two or more Linux machines running an OpenSSH server. Any reasonably current release supports everything here.
- A workstation to act as the CA. Pick a machine you control and do not expose to the internet.
- Root or sudo access on each server.
- Working key-based SSH access to every server today. Keep it in place until the new setup is verified.
- A consistent hostname scheme. This guide uses YOUR_DOMAIN as a placeholder for your internal domain, such as lab.YOUR_DOMAIN.
- Time synchronization (chrony, systemd-timesyncd, or ntpd) on every machine. Certificates are time-bound, so clock drift causes confusing failures.
Step 1: Create the two CA key pairs
Use separate CAs for users and hosts. If the user CA is ever compromised, an attacker still cannot impersonate your servers, and the reverse holds too.
mkdir -p ~/ca && chmod 700 ~/ca
ssh-keygen -t ed25519 -f ~/ca/user_ca -C 'home lab user CA'
ssh-keygen -t ed25519 -f ~/ca/host_ca -C 'home lab host CA'Set a strong passphrase when prompted. The private files (user_ca and host_ca, without the .pub suffix) are the crown jewels. Keep them encrypted, back them up to encrypted storage, and never copy them to servers. The .pub files are safe to distribute.
Step 2: Trust the user CA on each server
Copy the user CA public key to every server:
scp ~/ca/user_ca.pub [email protected]_DOMAIN:/etc/ssh/user_ca.pubThen edit /etc/ssh/sshd_config on that server and add:
TrustedUserCAKeys /etc/ssh/user_ca.pubBy default, a certificate is accepted for a login account only if that account name appears in the certificate’s principals. For finer control, point sshd at a principals file per account:
AuthorizedPrincipalsFile /etc/ssh/auth_principals/%uCreate the directory and a file for each account. For example, /etc/ssh/auth_principals/alice lists one principal per line, and any certificate carrying one of those principals may log in as alice:
sudo mkdir -p /etc/ssh/auth_principals
sudo chmod 755 /etc/ssh/auth_principals
echo 'alice' | sudo tee /etc/ssh/auth_principals/aliceValidate the configuration, then reload. Keep your existing session open while you do this, and test from a second terminal.
sudo sshd -t
sudo systemctl reload sshdThe service is named ssh on Debian and Ubuntu systems. Use sshd on RHEL-family systems.
Step 3: Sign each server’s host key
Host certificates let clients verify a server against your host CA, instead of trusting whatever key appears the first time they connect. Fetch the server’s ED25519 host public key to your CA workstation:
scp [email protected]_DOMAIN:/etc/ssh/ssh_host_ed25519_key.pub ~/ca/web01.pubSign it as a host certificate:
ssh-keygen -s ~/ca/host_ca -I 'web01 host' -h \
-n web01.YOUR_DOMAIN,web01,192.168.10.20 -V +90d ~/ca/web01.pubThe flags do the following. -s names the signing key. -I sets an identity string that appears in logs. -h marks this as a host certificate, which is required. -n lists every name and address clients will use to reach the host. -V sets the validity window. This command writes ~/ca/web01-cert.pub. Copy it back and register it:
scp ~/ca/web01-cert.pub [email protected]_DOMAIN:/etc/ssh/ssh_host_ed25519_key-cert.pubOn the server, add this line to /etc/ssh/sshd_config, then validate and reload as before:
HostCertificate /etc/ssh/ssh_host_ed25519_key-cert.pubRepeat this for every server. A host certificate must list every hostname or IP address that clients will type, or they will see a mismatch.
Step 4: Issue short-lived user certificates
On your workstation, sign your existing public key with the user CA:
ssh-keygen -s ~/ca/user_ca -I 'alice-laptop' -n alice -V +8h ~/.ssh/id_ed25519.pubThis writes ~/.ssh/id_ed25519-cert.pub. OpenSSH automatically offers a certificate when it sits beside the matching private key, so no client configuration is needed. Inspect what you issued with:
ssh-keygen -L -f ~/.ssh/id_ed25519-cert.pubEight hours is a deliberate choice. A stolen certificate is useful only until it expires, so shorter lifetimes shrink the damage window. Pick a lifetime that matches your habits, such as a workday or a week. For daily re-signing, a small helper script keeps the command consistent:
#!/bin/sh
ssh-keygen -s ~/ca/user_ca -I "$(whoami)-$(hostname)" -n "$(whoami)" -V +8h ~/.ssh/id_ed25519.pubEach run prompts for the CA passphrase, which is the point. Signing is a deliberate act that happens on a machine you control.
Step 5: Teach clients to trust the host CA
On each client machine, add a @cert-authority entry to ~/.ssh/known_hosts. It tells SSH to accept any host certificate signed by your host CA for matching names. Build the line from your host CA public key:
cat ~/ca/host_ca.pubPrefix that output with the marker and a pattern covering your domain, then append it to known_hosts:
@cert-authority *.YOUR_DOMAIN ssh-ed25519 AAAA...contents of host_ca.pub...Remove old per-host entries for these machines. Stale trust-on-first-use records create warnings that look like attacks and are not. Wildcard patterns do not match bare IP addresses, so add address patterns separately if you connect by IP.
Step 6: Plan for revocation and renewal
Short expiry is your primary revocation mechanism. A lost user certificate dies within hours. For immediate revocation, OpenSSH supports a key revocation list (KRL). Create an empty one:
ssh-keygen -k -f ~/ca/revoked_keys.krlAdd this to sshd_config on every server, then copy the file to each one:
RevokedKeys /etc/ssh/revoked_keys.krlTo revoke a certificate, find its serial number with ssh-keygen -L, record it in a spec file, and update the KRL. The spec syntax and update flags vary slightly by OpenSSH version, so check the ssh-keygen(1) man page on your systems before relying on it. Then redistribute the file and reload sshd.
Renewal is a calendar task. Host certificates on a 90-day validity need re-signing about every quarter. Host keys themselves do not change, so renewal means signing the same .pub file again and copying the result back. User certificates renew whenever you sign them. Set a reminder, or a cron job on the CA workstation that prints a warning when a host certificate is near expiry.
Verify it worked
Confirm that sshd reads your settings:
sudo sshd -T | grep -E 'trustedusercakeys|hostcertificate|authorizedprincipalsfile|revokedkeys'Confirm the host certificate’s validity and principals:
ssh-keygen -L -f ~/ca/web01-cert.pubConnect with verbose output and look for certificate lines:
ssh -v [email protected]_DOMAIN 2>&1 | grep -i -E 'certificate|authenticated'Then test the negative cases, because a setup that only works for good inputs proves little. Create a throwaway key, sign it for one minute, wait about two minutes, and try to connect with it. The connection should be refused:
ssh-keygen -t ed25519 -f /tmp/test_key -N ''
ssh-keygen -s ~/ca/user_ca -I expiry-test -n alice -V +1m /tmp/test_key.pub
ssh -i /tmp/test_key [email protected]_DOMAINRepeat with a certificate whose principal does not match the account you are logging into. That attempt should also fail. When both tests behave as expected and your certificate-based login works, you can remove the old keys from authorized_keys and consider setting PasswordAuthentication no in sshd_config.
Common mistakes
- Forgetting -h on host certificates. A user-style certificate presented as a host certificate will not be accepted. Always sign host keys with -h.
- Principal mismatches. A certificate issued with -n bob will not log in as alice unless the principals file says otherwise.
- Host names missing from -n. If you sign web01 but connect to web01.YOUR_DOMAIN, the host check fails. List every name and address you actually use.
- Mixing up the CAs. Keep the user CA and host CA separate, and do not trust the host CA for user logins or the reverse.
- Clock drift. A machine with the wrong time may see a valid certificate as expired or not yet valid. Check time sync before debugging anything else.
- Reloading without testing. Run sshd -t before every reload, and keep a root session open until you have confirmed a fresh login works.
- Losing the CA key or keeping it on servers. The CA private key belongs on your CA workstation, encrypted and backed up. Never on a server or a daily-use laptop.
- Re-signing after a host key change. If you reinstall a server and its host keys regenerate, the old host certificate no longer matches. Sign the new key.
- Setting no expiry. Host certificates with very long or no effective expiry defeat the point of the exercise. Use a finite validity and renew on schedule.
Takeaway
You now have one CA that vouches for your servers and your users, with trust that expires on its own schedule. The setup is small: two key pairs, a few lines of sshd configuration, and a signing step you run when a certificate is due. Each new machine is a single signing command rather than another file to audit, and removing access is a matter of waiting for expiry or updating a revocation list.
