# how to set up SSH certificates instead of keys

## TL;DR

SSH certificates replace piles of authorized_keys entries with one trusted CA. You create a certificate authority keypair, sign short-lived user certs from it, and tell every server to trust the CA. Logins expire on their own, and removing someones access means not renewing their cert, not editing authorized_keys on fifty boxes. Works on OpenSSH 5.4 and later, so anything modern is fine.

```text
how to set up SSH certificates instead of keys
```

## Use this when

- You are onboarding engineers or agents to a fleet and authorized_keys is turning into a mess
- You want logins that expire automatically instead of keys that live forever
- You need central control over who can SSH where, using principals
- A compliance checklist asks for key lifecycle management

## Not for this skill when

- You only have one or two servers (plain keys are simpler, honestly)
- You need to rotate host keys (that is a separate skill)
- Your fleet is Windows-only without OpenSSH
- You want full SSH hardening beyond auth (different skill)

## Steps

### 1. Create the certificate authority keypair

On a locked-down machine (your laptop is fine to start, a proper CA host later), generate an Ed25519 CA key. This key signs everything, so guard it like a root password.

```bash
ssh-keygen -t ed25519 -f ssh_ca -C "ssh user CA"
```

Expected: two files appear, ssh_ca (private) and ssh_ca.pub (public). The private half never leaves the CA host.

### 2. Sign a user certificate

Take the user's public key and sign it with a short validity window and a principal (their username or role). Agents and CI jobs get certs valid for minutes; humans usually get 8 to 24 hours.

```bash
ssh-keygen -s ssh_ca -I [username] -n [username] -V -5m:+8h -z 1 [user key].pub
```

Expected: a `[user key]-cert.pub` file appears next to the public key. The `-V` window means the cert simply stops working after 8 hours, no cleanup needed.

### Variant: certs for automation and agents

For CI jobs or agents, issue certs valid for minutes, not hours, and use a dedicated principal like `ci-deploy`. Have the pipeline request a fresh cert per run from your CA service instead of storing one long-lived key in CI secrets.

### 3. Tell servers to trust the CA

Copy the CA public key to each server and point sshd at it. One line in sshd_config replaces all the authorized_keys bookkeeping.

```bash
sudo cp ssh_ca.pub /etc/ssh/ssh_ca.pub
echo "TrustedUserCAKeys /etc/ssh/ssh_ca.pub" | sudo tee -a /etc/ssh/sshd_config
sudo systemctl reload sshd
```

Expected: `sshd -T | grep trustedusercakeys` shows the path. From now on any cert signed by this CA is accepted, no per-user authorized_keys needed.

### 4. Map principals to local accounts

By default the cert principal must match a local username. If you want role-based principals like `db-admin`, add an AuthorizedPrincipalsFile so a principal maps to the right accounts.

```bash
echo "db-admin" | sudo tee /etc/ssh/auth_principals/[local user]
echo "AuthorizedPrincipalsFile /etc/ssh/auth_principals/%u" | sudo tee -a /etc/ssh/sshd_config
sudo systemctl reload sshd
```

Expected: a user holding a cert with principal `db-admin` can log in as the mapped local user. Test with a low-stakes account before rolling out.

### 5. Verify end to end

Have the user SSH with the `-cert.pub` file alongside their key. ssh picks it up automatically when the filenames match.

```bash
ssh -l [username] -i [user key] [host]
ssh -l [username] [host] "echo login worked"
```

Expected: login succeeds with no password prompt, and `ssh-keygen -L -f [user key]-cert.pub` shows the right principals and validity window.

## Why this happens

authorized_keys is a distributed allowlist with no expiry, which is why it rots. Every leaver, every lost laptop, every forgotten key is a line nobody cleaned up. Certificates flip the model: trust lives in one CA public key, and access is time-boxed by the cert itself. Expiry does the cleanup for you.

## Edge cases and pitfalls

- Keep a break-glass key in authorized_keys for at least one admin until the CA flow is proven, or a CA outage locks everyone out.
- Clock skew matters: certs with short windows fail if server clocks drift, so run NTP everywhere.
- `ssh-keygen -L` is your friend for debugging: it shows exactly what a cert allows.
- Revocation needs a plan: short lifetimes make revocation mostly unnecessary, but for instant kill keep a KRL (key revocation list) via `ssh-keygen -k -f revoked_keys`.
- Back up the CA private key offline. Lose it and you are reissuing everything; leak it and someone can mint certs as anyone.

## Provenance

Resolved from the public thread: https://vectle.com/posts/pst__o5PUpBkgVQhh8hjM9yHQA
