SSH host certificates are a game-changer for verifying server identity without the hassle of managing known_hosts files.
Imagine you’re setting up a new server, or perhaps a whole fleet of them. Traditionally, when you first SSH into a new machine, your client prompts you to confirm the server’s host key. If you accept, that key gets added to your ~/.ssh/known_hosts file. The next time you connect, your SSH client checks if the server’s presented key matches the one in known_hosts. If it doesn’t, you get a scary warning, and rightly so – it could be a man-in-the-middle attack.
The problem is, managing known_hosts at scale is a nightmare. If a server’s host key legitimately changes (e.g., you re-provision the machine), every client connecting to it will get that warning. You’d have to manually update known_hosts on every single client, which is impractical for more than a handful of machines.
SSH host certificates solve this. Instead of trusting individual host keys, you trust a Certificate Authority (CA). The CA signs the server’s host key, creating a certificate. When you connect, the server presents its certificate. Your SSH client, configured to trust the CA, can then verify the certificate’s signature. If the CA is trusted, the client knows the server’s identity is legitimate, regardless of the specific host key it’s using.
Let’s see this in action. First, we need a CA. This can be a dedicated machine or even just a key pair on your workstation.
# On your CA machine: Generate CA private and public keys
ssh-keygen -t ed25519 -f ca_key
This creates ca_key (private) and ca_key.pub (public). Keep ca_key super secure!
Next, on the server you want to SSH into, generate its host keys if they don’t exist.
# On the server: Generate host keys (if not already present)
ssh-keygen -A
This typically creates /etc/ssh/ssh_host_rsa_key, /etc/ssh/ssh_host_rsa_key.pub, and similar for ECDSA and ED25519.
Now, we sign the server’s public host key with our CA.
# On your CA machine: Sign the server's public host key
ssh-keygen -s ca_key -I server_identity -h -n server.example.com,192.168.1.100 /etc/ssh/ssh_host_ed25519_key.pub
Let’s break down that ssh-keygen command:
-s ca_key: Specifies the CA private key to use for signing.-I server_identity: A unique identifier for this certificate (e.g., the hostname or a descriptive name).-h: Indicates this is a host certificate.-n server.example.com,192.168.1.100: A comma-separated list of principals (hostnames and/or IP addresses) that this certificate is valid for. The server must present one of these names during the connection./etc/ssh/ssh_host_ed25519_key.pub: The public host key of the server you are signing.
This command generates a ssh_host_ed25519_key-cert.pub file. You need to copy this certificate file to your server, placing it in the same directory as the corresponding private host key (e.g., /etc/ssh/). The SSH daemon will automatically pick it up.
Finally, on your client machine, you need to tell SSH to trust your CA’s public key. You do this by adding the CA’s public key to your ~/.ssh/known_hosts file, but with a special prefix:
# On your client machine: Add CA public key to known_hosts
echo "your_ca_public_key_content" >> ~/.ssh/known_hosts
You can get your_ca_public_key_content by running cat ca_key.pub on your CA machine. The line in known_hosts will look something like this:
@cert-authority *.example.com,192.168.1.0/24 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH... your_ca_public_key_content ...
Notice the @cert-authority prefix. This tells SSH that any certificate signed by this key is trusted for the specified principals (in this example, any host ending in .example.com or within the 192.168.1.0/24 subnet).
Now, when you SSH from your client to server.example.com:
ssh user@server.example.com
Your SSH client will see the server’s certificate, verify its signature against the trusted CA (@cert-authority entry), and check if the hostname/IP matches the principals listed in the certificate. If all checks pass, you’re connected without ever needing to manually add the server’s host key to known_hosts.
The key benefit here is that if the server’s host key changes (e.g., you re-image the server and it generates new keys), you don’t need to update anything on the clients. You just need to re-sign the new public host key with your existing CA and place the new certificate on the server. The clients, still trusting the CA, will accept the new certificate automatically.
One critical aspect is that the SSH daemon (sshd) on the server must be configured to present the certificate. This is usually automatic if the -cert.pub file is present alongside the private host key.
The principals you specify when signing (-n) are crucial. If you SSH to an IP address that wasn’t included in the -n list during signing, the connection will fail even if the CA is trusted. This is a security feature preventing a certificate valid for server1.example.com from being used to authenticate server2.example.com if they happen to share the same CA.
The next hurdle is automating the signing and deployment of certificates for a dynamic infrastructure.