| Tested on | Ubuntu 26.04 SSH client and host-CA workstation (192.168.56.121); RHEL 10.2 with two signed SSH servers (192.168.56.116, second sshd on port 2242) |
|---|---|
| Package | openssh-client 1:10.2p1-2ubuntu3.6 (Ubuntu); openssh-server-9.9p1-25.el10_2.x86_64 (RHEL) |
| Applies to | Any Linux, macOS, or Windows host with OpenSSH that supports ssh-keygen -s -h, HostCertificate, and @cert-authority in known_hosts |
| Privilege | sudo or root on SSH servers for HostCertificate and sshd reload; normal user on the CA workstation and client |
| Scope | OpenSSH-native SSH host certificates (host CA, ssh-keygen -h, HostCertificate, @cert-authority). Does not cover user certificate authentication, Vault, Smallstep, or X.509 TLS certificates. |
| Related guides | Generate SSH keys Test an SSH connection |
OpenSSH host certificates let SSH clients trust one host certificate authority (host CA) instead of storing a separate host key for every server in known_hosts. The CA signs each server's existing SSH host public key, and clients trust that CA through an @cert-authority entry.
A host CA is just an SSH key pair used to sign server host keys. It does not need to run as a network service. Clients trust the CA public key and verify certificates signed by its private key.
I ran the full path on Ubuntu 26.04 as both the CA workstation and SSH client, with RHEL 10.2 hosting two signed sshd listeners. The lab shows the usual unknown-host failure before trust, one-CA login to two servers, and ssh-keygen -L inspection of a host certificate.
Why known_hosts becomes difficult at scale
With plain SSH host keys, every client must learn every server key:
server1 host key ---> client known_hosts
server2 host key ---> client known_hosts
server3 host key ---> client known_hostsThat model is fine for a handful of hosts. With normal host-key verification, clients need a trusted copy of each server's host key, either in a user's known_hosts file or a centrally managed host-key database such as /etc/ssh/ssh_known_hosts. As the server fleet grows, adding and rotating those individual keys becomes harder to manage.
With host certificates, clients stop trusting each server key individually and trust the host CA instead:
Host CA
/ | \
signs signs signs
| | |
server1 server2 server3
\ | /
\ | /
v v v
Client trusts ONE host CA
(@cert-authority in known_hosts)After you trust the host CA once, any server that presents a valid certificate signed by that CA is accepted without enrolling its raw host key. Rotation becomes re-signing the host key and reloading sshd, not touching hundreds of clients.
Host key vs host certificate vs user certificate
OpenSSH uses the word certificate for two different jobs. Keep them separate.
| Item | What it proves | Where configured / trusted |
|---|---|---|
| Host key | "This is the server I expected" | Server has private key; client trusts public key in known_hosts |
| Host certificate | Same server identity, but signed by a CA | Server presents certificate via HostCertificate; client trusts host CA with @cert-authority |
| User certificate | "This user is authorized to log in" | Server trusts user CA via TrustedUserCAKeys |
The -h flag to ssh-keygen -s is what makes a host certificate. Without -h, you create a user certificate for OpenSSH user certificate authentication.
Use a different key pair for your host CA and your user CA. If one CA is compromised, the other trust domain should stay intact.
How SSH host certificates work
The flow has four moving parts:
- A host CA key pair on an admin workstation (private key signs; public key is trusted on clients).
- Each server's ordinary host key (
ssh_host_ed25519_keyand friends). - A host certificate file (
ssh_host_ed25519_key-cert.pub) that wraps the host public key and lists principals (hostnames and addresses). A host principal is simply a hostname or IP address that the certificate is valid for. - Client
known_hostsentries that trust the host CA with@cert-authority.
When you connect, sshd presents the host certificate. The client checks:
- The certificate is signed by a CA listed in
@cert-authorityfor this host pattern. - The certificate is still within its validity window.
- The hostname OpenSSH is validating must match one of the host certificate's principals. Normally this is the hostname or IP you connect to; options such as
HostKeyAliascan change it. - The certificate must belong to the same host key pair that
sshdis actually using.
server host key
↓ signed by
host CA
↓
host certificate
↓
client trusts CA (@cert-authority)Lab topology
The host CA private key stays on Ubuntu. RHEL runs two sshd instances — stock sshd on port 22 and a second listener on port 2242 — so you can see one CA signing two different host keys.
For the lab, server1 and server2 are two independent sshd listeners on the same RHEL VM. This is only to demonstrate that one CA can sign multiple server identities without requiring another VM.
Ubuntu 26.04 (192.168.56.121)
|-- host CA private key (signing only)
|-- SSH client + ~/.ssh/known_hosts (@cert-authority)
|
+---- SSH ---------------------------+
|
RHEL 10.2 (192.168.56.116) |
|-- server1.lab.example :22 |
|-- server2.lab.example :2242 |Add lab names on the Ubuntu client if DNS does not already resolve them:
grep -q server1.lab.example /etc/hosts || \
echo '192.168.56.116 server1.lab.example server2.lab.example' | sudo tee -a /etc/hostsReplace the lab IP address and hostnames with the values from your environment.
Create a host CA
Generate a dedicated CA key on the workstation that signs host certificates. Do not put this private key on SSH servers.
mkdir -p ~/ssh-host-cert-lab && cd ~/ssh-host-cert-labCreate the host CA with Ed25519:
ssh-keygen -t ed25519 -f host_ca -C host-ca-labssh-keygen prompts for a passphrase to protect the private key. For a disposable lab you can add -N "", but a real host CA private key should normally be passphrase-protected or stored in controlled signing infrastructure.
The command prints a key fingerprint and writes host_ca (private) and host_ca.pub (public). Distribute only host_ca.pub to clients inside @cert-authority lines.
Show the public half you will trust on clients:
cat host_ca.pubssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINPide79ed7k9/jpsoTLLxYBw/AsZ+9Hu+WF3Mcwx0gJ host-ca-labProtect the host CA private key as one of the most sensitive keys in the environment — offline media, hardware token, or a tightly ACL'd signing host, not world-readable on a shared login server.
Sign an SSH server host key
Copy the server's host public key to the CA workstation. Never copy the host private key to the CA machine, and never copy the host CA private key to servers.
On the RHEL server, publish the Ed25519 host public key. This copies only the .pub file — the server's private host key never leaves /etc/ssh:
sudo cp /etc/ssh/ssh_host_ed25519_key.pub /tmp/server1_host.pubFrom Ubuntu, pull that public key:
scp deepak@192.168.56.116:/tmp/server1_host.pub ~/ssh-host-cert-lab/Sign it as a host certificate. The -h flag is required:
cd ~/ssh-host-cert-lab
ssh-keygen -s host_ca -I server1 -h -z 1001 \
-n server1.lab.example,192.168.56.116 \
-V +30d server1_host.pubSigned host key server1_host-cert.pub: id "server1" serial 1001 for server1.lab.example,192.168.56.116 valid from 2026-09-23T13:55:00 to 2026-10-23T13:56:12| Option | Meaning |
|---|---|
-s host_ca |
Sign with this CA private key |
-h |
Create a host certificate (not a user certificate) |
-I server1 |
Certificate identifier shown in logs |
-z 1001 |
Serial number |
-n server1.lab.example,192.168.56.116 |
Valid hostnames and IPs |
-V +30d |
Validity period |
OpenSSH documents -h as the option that distinguishes a host certificate from a user certificate. Use a non-zero serial if you want simple KRL revocation by serial number. Serial 0 cannot be targeted by a KRL serial record, although the certificate can still be revoked by key ID or key material. Choose a lifetime that matches your rotation process.
Copy only the certificate back to the server:
scp server1_host-cert.pub deepak@192.168.56.116:/tmp/Inspect the host certificate
Before you install the certificate on sshd, read it with ssh-keygen -L:
ssh-keygen -L -f server1_host-cert.pubserver1_host-cert.pub:
Type: ssh-ed25519-cert-v01@openssh.com host certificate
Public key: ED25519-CERT SHA256:m0IbDhiWVYyfyiyaMJJjBMDlxsm5n5gRMU1SgW9gV4A
Signing CA: ED25519 SHA256:1w73ppzwmGidOmF488WovrAL8Q1+uyfUZrXCCrz+FNQ (using ssh-ed25519)
Key ID: "server1"
Serial: 1001
Valid: from 2026-09-23T13:55:00 to 2026-10-23T13:56:12
Principals:
server1.lab.example
192.168.56.116
Critical Options: (none)
Extensions: (none)The Type line must say host certificate. User certificates show a user certificate type instead. The Principals list is what the client matches against the hostname OpenSSH validates for the connection.
Configure sshd with HostCertificate
On the server, install the certificate beside the existing host key and tell sshd to present it.
sudo install -m 0644 /tmp/server1_host-cert.pub /etc/ssh/ssh_host_ed25519_key-cert.pubThe host certificate is public data, so 0644 is normal. The corresponding private ssh_host_ed25519_key must remain protected.
Add a drop-in that points at the certificate:
sudo tee /etc/ssh/sshd_config.d/50-host-cert.conf >/dev/null <<'EOF'
HostCertificate /etc/ssh/ssh_host_ed25519_key-cert.pub
EOFThe certificate must belong to the same host key pair that sshd is actually using. If you regenerate ssh_host_ed25519_key without re-signing, connections fail until you issue a new certificate.
Validate syntax before reload:
sudo sshd -tA clean check prints nothing. Confirm sshd read the setting:
sudo sshd -T | grep -i '^hostcertificate'hostcertificate /etc/ssh/ssh_host_ed25519_key-cert.pubReload the daemon:
# RHEL / Rocky / AlmaLinux
sudo systemctl reload sshd
# Ubuntu / Debian
sudo systemctl reload sshTrust the host CA with @cert-authority
On the Ubuntu client, add one line to ~/.ssh/known_hosts that trusts your host CA for the lab zone:
@cert-authority *.lab.example ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINPide79ed7k9/jpsoTLLxYBw/AsZ+9Hu+WF3Mcwx0gJ host-ca-labThe @cert-authority marker tells OpenSSH to accept host certificates signed by this key for the host pattern to the left. If users also connect directly by IP, add the IP to both the CA trust pattern and the certificate principals when you sign each host key.
Scope the pattern tightly. A bare @cert-authority * trusts that CA for every SSH server name — convenient in a lab, dangerous on a laptop that also visits the public internet.
Two names must line up. First, the @cert-authority host pattern must match the connection target. Second, the hostname OpenSSH validates must appear in the certificate principals. Passing only one of those checks is not enough.
How hostname matching works
Host certificate verification boils down to two rules:
- The
@cert-authorityhost pattern must match the connection target. - The certificate principal must match the validated host name.
If the server's real hostname is vm1.lab.example but the certificate only lists server1.lab.example, connecting to vm1.lab.example fails even when the CA is trusted. With ssh -vvv, the client prints the certificate details and then rejects the name:
debug1: Server host certificate: ssh-ed25519-cert-v01@openssh.com SHA256:..., serial 1001 ID "server1" CA ssh-ed25519 SHA256:...
No ED25519 host key is known for vm1.lab.example and you have requested strict checking.
Host key verification failed.Include every DNS name clients use, every load-balancer name, and the IP if operators connect by address.
For non-standard ports, known_hosts may use a bracketed [hostname]:port target. If a custom listener fails verification, use ssh -vvv to see the identity OpenSSH is checking. In this lab, ssh -p 2242 labuser@server2.lab.example worked with principal server2.lab.example and a *.lab.example @cert-authority entry — bracketed port-specific CA lines were not required.
Connect without adding an individual host key
Use one dedicated temporary known_hosts file so the before-and-after test uses the same trust store:
touch /tmp/host-ca-known_hostsWith no @cert-authority line yet, strict checking fails before authentication:
ssh -o StrictHostKeyChecking=yes \
-o UserKnownHostsFile=/tmp/host-ca-known_hosts \
-o GlobalKnownHostsFile=/dev/null \
root@server1.lab.example 'echo test'No ED25519 host key is known for server1.lab.example and you have requested strict checking.
Host key verification failed.Append the @cert-authority line from the previous section to /tmp/host-ca-known_hosts, then retry with the same options:
ssh -o StrictHostKeyChecking=yes \
-o UserKnownHostsFile=/tmp/host-ca-known_hosts \
-o GlobalKnownHostsFile=/dev/null \
root@server1.lab.example 'echo HOST_CERT_OK'HOST_CERT_OKYou did not paste server1's raw ssh-ed25519 AAAA... host key. The client trusted the CA signature instead.
Confirm the host certificate was actually used:
ssh -vv \
-o UserKnownHostsFile=/tmp/host-ca-known_hosts \
-o GlobalKnownHostsFile=/dev/null \
root@server1.lab.example true 2>&1 | grep -i 'Server host certificate'debug1: Server host certificate: ssh-ed25519-cert-v01@openssh.com SHA256:m0IbDhiWVYyfyiyaMJJjBMDlxsm5n5gRMU1SgW9gV4A, serial 1001 ID "server1" CA ssh-ed25519 SHA256:1w73ppzwmGidOmF488WovrAL8Q1+uyfUZrXCCrz+FNQ valid from 2026-09-23T13:55:00 to 2026-10-23T13:56:12That line shows OpenSSH accepted the signed host certificate from your CA, not a raw host key from elsewhere.
Add a second server signed by the same CA
Repeat the same process on a second SSH server: copy its host public key to the CA workstation, sign it with the same host_ca and -h, install the resulting *-cert.pub file, and set HostCertificate on that sshd. When the second hostname is already covered by the client's @cert-authority pattern, no new CA entry is required.
The second listener uses its own host private/public key pair. Signing the same key twice would demonstrate multiple names, not multiple host identities.
Sign the second host key with its own serial and principals:
cd ~/ssh-host-cert-lab
ssh-keygen -s host_ca -I server2 -h -z 1002 \
-n server2.lab.example,192.168.56.116 \
-V +30d server2_host.pubInstall the certificate on the second server the same way you did for server1 — HostCertificate in sshd_config or a drop-in, then sshd -t and reload.
From Ubuntu, connect to the second server. In the lab it listens on port 2242 at the same IP as server1:
ssh -p 2242 -o StrictHostKeyChecking=yes labuser@server2.lab.example 'echo SERVER2_OK'SERVER2_OKThe client trusted the second server without adding another raw host key.
Rotate a host key or certificate
Rotation has two common shapes.
Re-sign the same host key when you only need a new expiry date or updated principals. Issue a new serial with -z for every certificate you mint — do not reuse the serial from the certificate you are replacing:
ssh-keygen -s host_ca -I server1 -h -z 1003 \
-n server1.lab.example,192.168.56.116 \
-V +30d /path/to/ssh_host_ed25519_key.pubReplace /etc/ssh/ssh_host_ed25519_key-cert.pub, run sshd -t, and reload sshd.
Generate a new host key when the private host key may be exposed. Generate to a new path first instead of overwriting the active key while you may still be connected remotely:
sudo ssh-keygen -t ed25519 -f /etc/ssh/ssh_host_ed25519_key.new -N ""Sign the new .pub file, install the matching certificate, then perform a controlled replacement of the live host key and certificate. Clients that trusted only the old raw host key must be updated; clients that trust the host CA automatically accept the new certificate once it is signed.
Keep the host CA private key offline during normal operation. For larger environments, use a dedicated signing system or controlled CA service with audit logging.
What happens if the certificate expires?
The certificate itself is rejected once it is outside its validity period. A connection may still succeed separately if the client also trusts the server's raw host key.
Sign with shorter windows for production hosts (-V +30d or -V +90d) and automate re-signing before expiry. ssh-keygen -L shows the Valid: range so you can script alerts.
SSHFP DNS records (optional alternative)
Some sites publish host keys through DNSSEC-signed SSHFP records instead of a host CA. That model depends on DNS validation (VerifyHostKeyDNS yes) rather than @cert-authority in known_hosts. Host CAs keep trust entirely inside SSH configuration. Most fleets pick one primary model per zone.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
No matching CA found. Retry with plain key |
@cert-authority host pattern does not match the connection target, wrong CA key, or certificate was rejected |
Check the exact hostname or [host]:port with ssh -vvv, verify the CA line, then inspect certificate principals with ssh-keygen -L |
No ED25519 host key is known for … before any CA line |
No @cert-authority and no raw host key |
Add @cert-authority for the name pattern, or enroll the raw key once |
Host key verification failed with @cert-authority present |
Hostname not in certificate principals, or CA public key mismatch | Fix -n principals when signing; confirm host_ca.pub in known_hosts matches the signer |
Certificate invalid / validity errors |
Expired -V window |
Re-sign with a new validity range and reload sshd |
sshd fails sshd -t after adding HostCertificate |
Certificate not signed from a configured HostKey, or path not readable |
Confirm the certificate was signed from the .pub file belonging to one of the configured HostKey private keys, and verify the HostCertificate path is readable |
| Connection works by IP but not by hostname | IP listed in principals but DNS name missing | Add the FQDN to -n when signing |
Certificate shown in ssh -v but verification still fails |
@cert-authority pattern does not cover this hostname |
Broaden the host pattern or add the name to the @cert-authority prefix list |
| Host cert behaves differently on a non-default port | OpenSSH may validate a different host identity for lookup | Inspect ssh -vvv or use HostKeyAlias to see or control the identity used for host-key lookup and certificate validation |
| Signed cert shows user certificate type | Missing -h on ssh-keygen -s |
Re-sign with -h for host certificates |
| Connection succeeds despite certificate problems | Raw host key still trusted in known_hosts |
OpenSSH may retry verification using the underlying raw host key. Use ssh -vvv during rollout and avoid maintaining both trust models for the same host |
When diagnosis is unclear, run one failing connection with ssh -vvv and read the Server host certificate: line together with the final verification error.
Summary
OpenSSH host certificates let clients trust one host CA through @cert-authority in known_hosts instead of copying every server host public key. The signing path is ssh-keygen -s host_ca -h with principals that match real connection names, then HostCertificate on each sshd instance.
The lab on Ubuntu and RHEL showed the expected failure before CA trust, successful logins to two servers signed by the same CA, and rejection when the connection name failed either the @cert-authority pattern or the certificate principals. Inspect certificates with ssh-keygen -L, assign non-zero serials with -z, and keep host CAs separate from user CAs used for OpenSSH user certificate authentication.
For fleet rollout, scope @cert-authority patterns narrowly, automate re-signing before -V expiry, assign non-zero serials with -z for revocation, and protect the host CA private key as one of the most sensitive keys in the environment.
References
- ssh-keygen(1) — CERTIFICATES —
-hhost certificates,-nprincipals,-Vvalidity - sshd_config(5) — HostCertificate — server presentation of host certificates
- ssh(1) — known_hosts —
@cert-authoritymarker - OpenSSH PROTOCOL.certkeys — certificate format
Frequently Asked Questions
What is the difference between a host certificate and a user certificate in OpenSSH?
A host certificate proves server identity to clients. Sign it with ssh-keygen -s -h and configure HostCertificate on sshd. Clients trust the signing host CA through @cert-authority in known_hosts. A user certificate proves user identity to servers through TrustedUserCAKeys. The key types share tooling but solve opposite directions of trust.
What does ssh-keygen -h do when signing?
The -h flag tells ssh-keygen to produce a host certificate instead of a user certificate. Without -h, the output is for user authentication and will not work as a HostCertificate file on sshd.
Can I use the same CA for host and user certificates?
Technically yes, but you should not. Keep a dedicated host CA and a dedicated user CA so a compromise in one domain does not break both server identity and user login trust.
Do I still need known_hosts with host certificates?
Yes. Clients still use known_hosts, but they store an @cert-authority line that names the host CA public key instead of every individual server host key. You can scope the pattern to *.example.com or a comma-separated host list.
What happens if the hostname does not match certificate principals?
SSH host verification fails even when the certificate is signed by a trusted host CA. The @cert-authority host pattern must match, and the hostname OpenSSH validates must appear in the principals list from ssh-keygen -n. Options such as HostKeyAlias can change the validated name. Fixing only one of those checks is not enough.
What does No matching CA found. Retry with plain key mean?
The client rejected the presented host certificate for the connection target you used — often because the @cert-authority pattern does not match, the CA public key in known_hosts is wrong, or the certificate failed another check. OpenSSH may then retry with the underlying raw host key. Run ssh -vvv and compare the connection name to both the @cert-authority line and ssh-keygen -L principals.
How do I check whether sshd is presenting a host certificate?
From the server, run sshd -T | grep -i '^hostcertificate' and confirm the path. From a client, run ssh -vv and look for a Server host certificate debug line. On the CA workstation, run ssh-keygen -L -f ssh_host_ed25519_key-cert.pub and confirm the Type line says host certificate.

