<!-- https://pkizerotohero.com/guides/ssh-certificates/ | Markdown mirror of the HTML page | PKI Zero to Hero, by Duyutech BV -->

> Replace known_hosts prompts and authorized_keys sprawl with OpenSSH certificates: host and user CAs, signing, principals, expiry and revocation lists.

Source: https://pkizerotohero.com/guides/ssh-certificates/ · Last reviewed: 2026-10-20

How-to guide · Labs and advanced

# How to use SSH certificates instead of known\_hosts

**In one sentence:**

You will have OpenSSH host and user certificates signed by your own CAs, so clients trust servers without first-use prompts and servers trust short-lived user certificates.

Task

[Labs and advanced](https://pkizerotohero.com/guides/#task-advanced)

Platforms

- Linux
- macOS
- Windows

Who it is for

- [IT ops](https://pkizerotohero.com/start/#itops)
- [Expert](https://pkizerotohero.com/start/#expert)
- [Developer](https://pkizerotohero.com/start/#developer)

Level

Practitioner · 6 steps

## Before you start

- OpenSSH on clients and servers. Any current version works; this guide was tested with OpenSSH 8.9 and 9.9.
- Administrator access to the servers’ `sshd_config`.
- A safe place for two CA private keys: an offline machine, a hardware token or a signing service. Whoever holds the user CA key can log in anywhere that trusts it.

Why this matters: plain SSH relies on [trust on first use](https://pkizerotohero.com/handbook/glossary/#trust-on-first-use). The first connection asks “The authenticity of host … can’t be established”, and almost everyone types `yes`. Each server also keeps its own `authorized_keys` list, which rarely gets cleaned up. An [SSH certificate](https://pkizerotohero.com/handbook/glossary/#ssh-certificate) fixes both: a CA vouches for host and user keys, with names and an expiry date.

SSH certificates are not X.509 certificates. They use OpenSSH’s own simpler format, but the idea is the same: a trusted signer binds a key to a name for a limited time.

## Steps

### 1. Create a host CA and a user CA

Use two separate CAs, so that the key that vouches for servers can never log anyone in:

```bash
ssh-keygen -t ed25519 -f host_ca -C "Example host CA"
ssh-keygen -t ed25519 -f user_ca -C "Example user CA"
```

Give both a strong passphrase when asked. `host_ca.pub` and `user_ca.pub` are public and go to every machine; the private halves stay on the signing machine.

### 2. Sign each server’s host key

Copy the server’s public host key to the signing machine, sign it with `-h` (host certificate), and list every name clients use with `-n`:

```bash
ssh-keygen -s host_ca -I web01.lab.test -h -n web01.lab.test,localhost -V +52w ssh_host_ed25519_key.pub
```

```text
Signed host key ssh_host_ed25519_key-cert.pub: id "web01.lab.test" serial 0 for web01.lab.test,localhost valid from 2026-10-20T04:36:00 to 2027-10-19T04:37:01
```

Our lab server also answered as `localhost`, so that name is in the list; a real server lists its short name, its full name and any aliases. Copy `ssh_host_ed25519_key-cert.pub` back to `/etc/ssh/` on the server and add one line to `/etc/ssh/sshd_config`:

```text
HostCertificate /etc/ssh/ssh_host_ed25519_key-cert.pub
```

### 3. Make clients trust the host CA

Add one `@cert-authority` line to `known_hosts`, globally in `/etc/ssh/ssh_known_hosts` or per user in `~/.ssh/known_hosts` (on Windows, `%USERPROFILE%\.ssh\known_hosts`). The pattern says which host names this CA may vouch for:

```text
@cert-authority *.lab.test ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA… Example host CA
```

The rest of the line is the content of `host_ca.pub`. From now on, any server whose certificate is signed by this CA and lists the name you typed is accepted without a prompt, including servers that were rebuilt with new keys.

### 4. Issue short-lived user certificates

Users keep their own key pair. The CA signs the public key with a key ID (for logs), the principals (account names it may use) and a short validity. A unique serial number with `-z` makes revocation by serial possible:

```bash
ssh-keygen -s user_ca -I alice@example.com -n alice,deploy -V +8h -z 1001 id_ed25519.pub
ssh-keygen -L -f id_ed25519-cert.pub
```

```text
id_ed25519-cert.pub:
        Type: ssh-ed25519-cert-v01@openssh.com user certificate
        Public key: ED25519-CERT SHA256:fv87OrfS8dOFa/vp4pMtWjrbvCMrCb57fZ4MQoox024
        Signing CA: ED25519 SHA256:5xODG4rQWAa3niuhYfPuIAzl9VpmBr4/L4I5ZXJeOTY (using ssh-ed25519)
        Key ID: "alice@example.com"
        Serial: 1001
        Valid: from 2026-10-20T04:36:00 to 2026-10-20T12:37:33
        Principals: 
                alice
                deploy
        Critical Options: (none)
        Extensions: 
                permit-X11-forwarding
                permit-agent-forwarding
                permit-port-forwarding
                permit-pty
                permit-user-rc
```

The client finds `id_ed25519-cert.pub` automatically when it sits next to `id_ed25519`. Eight hours covers a working day; the next day the user gets a new certificate. Restrict what a certificate allows with `-O`, for example `-O clear -O permit-pty` to drop forwarding.

### 5. Make servers trust the user CA

Copy `user_ca.pub` to each server and add to `sshd_config`:

```text
TrustedUserCAKeys /etc/ssh/user_ca.pub
AuthorizedPrincipalsFile /etc/ssh/principals/%u
RevokedKeys /etc/ssh/revoked.krl
```

- With `TrustedUserCAKeys` alone, a certificate may log in to an account whose name is one of its principals.
- `AuthorizedPrincipalsFile` changes that to an explicit list per account: `/etc/ssh/principals/deploy` might contain `deploy` and `oncall`. Accounts without a file accept no certificates.
- `RevokedKeys` points at a key revocation list (step 6). Create it before you add the line: `sshd` refuses keys when the file cannot be read.

Check the configuration and reload: `sudo sshd -t && sudo systemctl reload ssh` (the service is called `sshd` on some distributions).

### 6. Revoke when needed

A key revocation list (KRL) can revoke certificates by serial number, by key ID or by key. Revoke serial 1001 from the user CA, then check it next to a second certificate for the same key with another serial (`id_alice-cert.pub`):

```bash
echo "serial: 1001" > revoke.txt
ssh-keygen -k -f revoked.krl -s user_ca.pub revoke.txt
ssh-keygen -Q -f revoked.krl id_ed25519-cert.pub id_alice-cert.pub
```

```text
Revoking from revoke.txt
id_ed25519-cert.pub (alice laptop): REVOKED
id_alice-cert.pub (alice laptop): ok
```

Distribute `revoked.krl` to every server. With short-lived certificates, most departures need no revocation at all: you simply stop issuing.

## Check it worked

Connect with verbose output and look for the certificate lines. Our lab server listened on port 2222, its `known_hosts` held only the `@cert-authority` line (with the pattern `[localhost]:2222`), and the account’s principals file allowed `deploy`:

```bash
ssh -v -p 2222 localhost true
```

```text
debug1: Server host certificate: ssh-ed25519-cert-v01@openssh.com SHA256:amKQ68HAp56XNHO9957Og3G3CU+xr82HdvwD3VFigmE, serial 0 ID "web01.lab.test" CA ssh-ed25519 SHA256:r/i7Yd+fhYPjPqVrBuLr6q3yV9ng3JWbyT0PFPlQr8Q valid from 2026-10-20T04:36:00 to 2027-10-19T04:37:01
debug1: Host '[localhost]:2222' is known and matches the ED25519-CERT host certificate.
debug1: Server accepts key: …/id_ed25519 ED25519-CERT SHA256:fv87OrfS8dOFa/vp4pMtWjrbvCMrCb57fZ4MQoox024 explicit
Authenticated to localhost ([127.0.0.1]:2222) using "publickey".
```

The host was trusted through its certificate, with no prompt, and the server accepted the user’s certificate rather than a plain key.

## If something goes wrong

On the server, `sshd` logs the reason a certificate was refused (`journalctl -u ssh` or `/var/log/auth.log`). The client only says `Permission denied (publickey)`. Real messages from our lab:

- **`Certificate does not contain an authorized principal`**: the certificate’s principals do not match the account, or the account’s `AuthorizedPrincipalsFile`. Re-issue with the right `-n`, or update the file.
- **`Certificate invalid: expired`**: issue a new certificate. If they expire mid-task, make the validity a little longer, not months.
- **`… revoked by file /etc/ssh/revoked.krl`**: the certificate’s serial, key ID or key is in the KRL.
- **`No ED25519 host key is known for … and you have requested strict checking`** on the client: no `@cert-authority` line matches the host name you typed, or the host certificate does not list that name. Compare `ssh-keygen -L -f` on the host certificate with the name in your command.
- **The prompt still appears for some servers**: those servers present no certificate (check `HostCertificate` and reload `sshd`), or the name you typed is missing from the certificate’s principals. Old plain entries in `known_hosts` can also keep the client on the plain key; remove them with `ssh-keygen -R <host>`.

## Why this works

A certificate is a CA’s signature over a public key, a name and a validity period ([binding identity to keys](https://pkizerotohero.com/handbook/binding-identity/)). Clients trust the host CA’s key instead of each server’s key, and servers trust the user CA instead of copies of every user’s key, exactly as browsers trust a root instead of every website. Keeping the CA keys offline and the lifetimes short follows the same logic as a [private CA](https://pkizerotohero.com/handbook/private-ca/), and the KRL plays the role of a CRL ([revocation](https://pkizerotohero.com/handbook/revocation/)).

## Go deeper

- [OpenBSD manual: ssh-keygen(1)](https://man.openbsd.org/ssh-keygen#CERTIFICATES): signing, `-n`, `-V`, `-O` options and KRLs.
- [OpenBSD manual: sshd\_config(5)](https://man.openbsd.org/sshd_config): `HostCertificate`, `TrustedUserCAKeys`, `AuthorizedPrincipalsFile`, `RevokedKeys`.
- [OpenBSD manual: sshd(8), SSH\_KNOWN\_HOSTS FILE FORMAT](https://man.openbsd.org/sshd#SSH_KNOWN_HOSTS_FILE_FORMAT): the `@cert-authority` marker.
- [IETF draft: SSH Certificate Format](https://datatracker.ietf.org/doc/draft-ietf-sshm-cert/): the OpenSSH certificate format, written up for the IETF’s SSH working group.

**Early edition.** Checked against the standards; an independent expert review is in progress. Found a mistake? [Tell us](https://pkizerotohero.com/contact/?topic=content-error&page=/guides/ssh-certificates/).

Last reviewed 20 October 2026Edition 0.9

## Keep going

### Learn why it works

- [Chapter 7: Whose key is it? Binding identity to keys](https://pkizerotohero.com/handbook/binding-identity/)
- [Chapter 22: A CA of your own: private two-tier PKI](https://pkizerotohero.com/handbook/private-ca/)
- [Chapter 16: Taking it back: CRLs, OCSP and stapling](https://pkizerotohero.com/handbook/revocation/)

### Topics

- [Private PKI](https://pkizerotohero.com/topics/private-pki/)

## More guides: Labs and advanced

- [How to run a small private CA with step-ca (lab)](https://pkizerotohero.com/guides/private-ca-step-ca/)
- [How to build a test CA with OpenSSL (lab only)](https://pkizerotohero.com/guides/openssl-test-ca/)
- [How to sign code with signtool and cosign](https://pkizerotohero.com/guides/code-signing-signtool-cosign/)
- [How to test post-quantum hybrid TLS](https://pkizerotohero.com/guides/post-quantum-tls-test/)
