How SSH Certificates Work
Traditional SSH authentication revolves around raw public keys. You generate a key pair, copy the public half into ~/.ssh/authorized_keys on each target host, and manage trust through file distribution. This works for small environments. It collapses at scale. SSH certificates solve this by introducing a Certificate Authority (CA) model that is entirely separate from the X.509 PKI used by TLS. OpenSSH certificates are simpler, more flexible, and purpose-built for the SSH ecosystem — and they have been available since OpenSSH 5.4 (2010), yet remain underused in most organizations.
The Problem with Raw Public Keys
With raw public key authentication, every trust relationship is a point-to-point binding: this specific public key is authorized to access this specific account on this specific host. The result is an O(n×m) management problem — n users times m servers. Every time you add a server, you must distribute authorized keys to it. Every time an employee leaves, you must remove their key from every server. Every time a key is rotated, the same distribution happens again.
Host key verification has the same problem in reverse. The first time a user connects to a new server, they see the The authenticity of host ... can't be established prompt. Most people type "yes" without verifying the fingerprint — training users to blindly accept TOFU (trust on first use) is an invitation for man-in-the-middle attacks. Even organizations that verify fingerprints out-of-band must maintain a known_hosts registry and update it every time a host key rotates.
SSH certificates eliminate both problems by introducing a single trust anchor: the CA key.
Two Types of SSH Certificates
OpenSSH supports two distinct certificate types, each solving a different half of the trust equation:
User certificates authenticate users to servers. Instead of listing every user's public key in authorized_keys, the server trusts a single CA public key. Any user who presents a certificate signed by that CA is accepted (subject to principal matching). This flips the model from O(n×m) to O(1) configuration per server.
Host certificates authenticate servers to clients. Instead of maintaining a known_hosts file with every server's fingerprint, the client trusts the CA and verifies that the server's host certificate is signed by it. No more TOFU prompts. No more stale known_hosts entries when servers are reprovisioned.
The OpenSSH Certificate Format
OpenSSH certificates are not X.509. They use a custom binary format defined by OpenSSH, which is intentionally simpler than the ASN.1/DER encoding of X.509. You can inspect a certificate with ssh-keygen -L:
$ ssh-keygen -L -f id_ed25519-cert.pub
id_ed25519-cert.pub:
Type: [email protected] user certificate
Public key: ED25519-CERT SHA256:xK3...
Signing CA: ED25519 SHA256:mR7... (using ssh-ed25519)
Key ID: "[email protected]"
Serial: 42
Valid: from 2026-04-24T00:00:00 to 2026-04-25T00:00:00
Principals:
alice
deploy
Critical Options: (none)
Extensions:
permit-pty
permit-user-rc
permit-agent-forwarding
permit-port-forwarding
The certificate is a single line in base64, appended with the certificate type identifier. Its wire format contains these fields:
- Nonce: A random value to prevent signature hash collisions.
- Public key: The user's or host's public key that this certificate vouches for.
- Serial: An arbitrary 64-bit number set by the CA. Useful for revocation lists.
- Type: 1 for user certificates, 2 for host certificates.
- Key ID: A free-form string for audit logging. Typically contains the user's identity and issuance metadata.
- Principals: The list of usernames (for user certs) or hostnames (for host certs) this certificate is valid for.
- Valid after / Valid before: The time window in which the certificate is accepted. This is the mechanism that enables short-lived certificates.
- Critical options: Constraints that must be understood and enforced. If a server encounters a critical option it does not recognize, it must reject the certificate.
- Extensions: Permissions granted to the certificate holder. Unlike critical options, unknown extensions are safely ignored.
- Signature: The CA's signature over all preceding fields.
This simplicity is a feature. X.509 certificates have extension mechanisms so complex that entire systems had to be built to audit how CAs use them. OpenSSH's format covers 95% of use cases with a fraction of the complexity.
Setting Up a Certificate Authority
An SSH CA is just a key pair. There is no enrollment ceremony, no CSR process, no certificate chain. You generate a key, protect it, and start signing.
Creating the CA Key
# Generate the CA key pair (protect this with a strong passphrase)
ssh-keygen -t ed25519 -f ca_key -C "SSH CA for example.com"
# This produces:
# ca_key (private key — guard this carefully)
# ca_key.pub (public key — distribute freely)
In practice, you want separate CA keys for user certificates and host certificates. This limits blast radius: if the user CA key is compromised, attackers can forge user identities but cannot impersonate hosts (and vice versa).
Signing Host Certificates
# Sign the host's public key
ssh-keygen -s ca_key \
-I "web-01.example.com-20260424" \
-h \
-n web-01.example.com,web-01,10.0.1.5 \
-V +52w \
/etc/ssh/ssh_host_ed25519_key.pub
# This creates: /etc/ssh/ssh_host_ed25519_key-cert.pub
The -h flag marks this as a host certificate. The -n flag lists the hostnames the certificate is valid for — clients will reject the certificate if the hostname they connected to is not in this list. -V +52w sets the validity to 52 weeks.
Configure sshd to present the certificate:
# /etc/ssh/sshd_config
HostCertificate /etc/ssh/ssh_host_ed25519_key-cert.pub
Configure clients to trust the CA:
# ~/.ssh/known_hosts (or system-wide /etc/ssh/ssh_known_hosts)
@cert-authority *.example.com ssh-ed25519 AAAA...CA_PUBLIC_KEY...
After this, clients connecting to any *.example.com host will verify the host certificate against the CA rather than checking individual fingerprints. No more TOFU.
Signing User Certificates
# Sign the user's public key
ssh-keygen -s ca_key \
-I "[email protected]" \
-n alice,deploy \
-V +8h \
-O no-port-forwarding \
~/.ssh/id_ed25519.pub
# This creates: ~/.ssh/id_ed25519-cert.pub
The -n flag lists principals (usernames) this certificate is valid for. The user can authenticate as alice or deploy on any server that trusts this CA, but not as root or any other username.
Configure sshd to trust user certificates:
# /etc/ssh/sshd_config
TrustedUserCAKeys /etc/ssh/ca_user_key.pub
That is the entire server-side configuration for user certificate authentication. No authorized_keys management. No key distribution automation. One line in sshd_config.
Principals: The Authorization Layer
Principals are how SSH certificates separate authentication from authorization. The CA certifies that this key belongs to Alice, and the principals list says Alice is allowed to act as these specific usernames.
By default, if a certificate has principals, the user can only authenticate as one of those principals. If the certificate has no principals, it is valid for any username — which is dangerous. Production CAs should always set principals.
Servers can further restrict this with AuthorizedPrincipalsFile:
# /etc/ssh/sshd_config
TrustedUserCAKeys /etc/ssh/ca_user_key.pub
AuthorizedPrincipalsFile /etc/ssh/auth_principals/%u
# /etc/ssh/auth_principals/deploy
deploy
sre-team
release-engineering
This file lists which principals are accepted for a given local account. If Alice's certificate has principal sre-team and the server's auth_principals/deploy file includes sre-team, Alice can log in as deploy. This enables role-based access: instead of naming specific users, you assign group principals like sre-team or oncall and manage membership at the CA level.
Critical Options and Force-Command
Critical options are constraints baked into the certificate that the server must enforce. The most important one is force-command:
# Issue a certificate that can only run backups
ssh-keygen -s ca_key \
-I "backup-agent-20260424" \
-n backup \
-V +1h \
-O force-command="/usr/local/bin/run-backup" \
-O no-port-forwarding \
-O no-pty \
-O no-agent-forwarding \
-O no-x11-forwarding \
backup_key.pub
This certificate can only execute /usr/local/bin/run-backup. Even if the holder tries ssh backup@server /bin/bash, the server will execute the forced command instead. Combined with no-pty (no interactive terminal) and no-port-forwarding, this creates a tightly scoped credential — the backup agent can trigger backups and nothing else.
The source-address critical option restricts which IP addresses can use the certificate:
ssh-keygen -s ca_key \
-I "alice-vpn-only" \
-n alice \
-V +8h \
-O source-address=10.0.0.0/8,172.16.0.0/12 \
alice_key.pub
If Alice tries to use this certificate from a public IP address, the server will reject the authentication attempt. This is a defense-in-depth measure for environments where certificates might be exfiltrated from a compromised workstation.
Short-Lived Certificates
The validity window on SSH certificates is their single most transformative feature. With raw public keys, revocation is a distribution problem: you have to push an updated authorized_keys or a Key Revocation List (KRL) to every server. With short-lived certificates, revocation becomes unnecessary — the certificate expires on its own.
A certificate issued with -V +8h is valid for eight hours. After that, it is cryptographically inert. Even if an attacker steals it, they have a shrinking window of opportunity. If the validity is reduced to minutes, the credential is essentially a session token with the properties of a public key.
This transforms SSH credential management from a state synchronization problem into a token issuance problem. The CA does not need to maintain revocation lists. Servers do not need to poll for revocation updates. The clock handles it.
Netflix BLESS: Pioneering Short-Lived SSH Certificates
Netflix open-sourced BLESS (Bastion's Lambda Ephemeral SSH Service) in 2016, and it became the canonical reference implementation for short-lived SSH certificate issuance. BLESS runs as an AWS Lambda function, which means the CA signing key exists only in memory during the brief Lambda invocation — there is no persistent server to compromise.
The architecture is elegantly simple:
- An engineer authenticates to AWS via their corporate IdP (SSO + MFA).
- They invoke the BLESS Lambda with their public key and requested username.
- BLESS validates the request against IAM policies, generates a certificate with a 5-minute validity window, signs it with the CA key (decrypted from an encrypted blob using KMS), and returns the certificate.
- The engineer uses the certificate to SSH into any server that trusts the CA.
Key design decisions in BLESS:
- KMS-encrypted CA key: The CA private key is stored as an encrypted blob in the Lambda deployment package. It is decrypted at invocation time using AWS KMS, which provides audit logging and IAM-based access control for the decryption key.
- 5-minute certificates: The default validity is just long enough for a user to establish their SSH session. Even if the certificate is stolen, the window for abuse is negligible.
- IP-restricted: BLESS stamps the requesting user's IP address into the certificate's
source-addresscritical option, so the certificate cannot be used from a different network location. - No persistent state: Lambda functions are ephemeral. There is no daemon to patch, no server to harden, no persistent process that might be compromised and used for ongoing certificate forgery.
BLESS demonstrated that SSH certificate issuance could be reduced to a stateless function call, and that extremely short validity periods (minutes, not hours or days) are practical for interactive use.
Facebook and Uber: Scaling Certificate Infrastructure
Facebook's SSH certificate system operates at a different scale entirely. With hundreds of thousands of servers and tens of thousands of engineers, the challenges go beyond issuance into realm management and operational segmentation.
Facebook's approach uses a tiered principal model where certificates carry principals that map to infrastructure tiers rather than individual server hostnames. An engineer on the web tier gets a certificate with a web-tier principal; the production database servers accept a db-admin principal. This decouples certificate issuance from host inventory — the CA does not need to know every hostname, and adding new servers requires only that they be assigned to the correct tier.
Uber's Pam Ussh system took a different architectural path, integrating certificate validation directly into the PAM (Pluggable Authentication Module) stack. This allowed them to layer SSH certificate authentication alongside their existing PAM modules for audit logging, session recording, and multi-factor enforcement. Rather than replacing their PAM configuration, they added certificate validation as another PAM module that runs during the authentication phase.
Both systems share common architectural principles with BLESS: centralized issuance, short validity periods, principals-based authorization, and integration with the corporate identity provider. The differences are in how they handle the operational complexity of very large, heterogeneous environments.
Certificate Extensions and Permissions
The extensions field in an SSH certificate controls what capabilities the certificate holder has once authenticated. These are additive permissions — absent by default.
| Extension | Effect when present |
|---|---|
permit-pty | Allows requesting a pseudo-terminal (interactive shell) |
permit-agent-forwarding | Allows forwarding the SSH agent |
permit-port-forwarding | Allows local, remote, and dynamic port forwarding |
permit-X11-forwarding | Allows X11 display forwarding |
permit-user-rc | Allows execution of ~/.ssh/rc |
no-touch-required | Disables the FIDO2 touch requirement for security keys |
When you sign a certificate with ssh-keygen, all extensions are enabled by default. To strip them, use -O flags that negate the defaults:
# Minimal certificate: no PTY, no forwarding, no agent
ssh-keygen -s ca_key \
-I "ci-runner-20260424" \
-n deploy \
-V +30m \
-O clear \
-O permit-pty \
deploy_key.pub
The -O clear flag removes all extensions, then -O permit-pty adds back only the pseudo-terminal permission. The resulting certificate can get a shell but cannot forward ports, forward the agent, or execute user rc scripts. This is the principle of least privilege applied at the credential level.
Certificate Revocation
Short-lived certificates minimize the need for revocation, but they do not eliminate it entirely. If a CA key is compromised, or if a certificate needs to be invalidated before its natural expiry (e.g., an employee is terminated mid-day), OpenSSH provides Key Revocation Lists (KRLs).
# Create a KRL revoking a specific certificate by serial number
ssh-keygen -k -f /etc/ssh/revoked_keys -s ca_key.pub -z 42
# Revoke by key ID
ssh-keygen -k -f /etc/ssh/revoked_keys -s ca_key.pub id_ed25519.pub
# Configure sshd to check the KRL
# /etc/ssh/sshd_config
RevokedKeys /etc/ssh/revoked_keys
KRLs are binary files that support efficient lookup by serial number, key ID, or raw key hash. They are far more compact than text-based lists: a KRL with a million revoked serials is only a few megabytes, because serial ranges are stored as bitmap intervals.
The operational challenge with KRLs is distribution — the same problem that certificates were supposed to solve. Every server needs an up-to-date copy of the KRL. Organizations that use KRLs typically distribute them via configuration management (Puppet, Chef, Ansible) or a pull-based sync mechanism. This is why short validity periods are so strongly preferred: a 10-minute certificate eliminates the need for KRL distribution in most scenarios.
Certificate Rotation and Key Ceremony
Rotating the CA key itself is the most disruptive operation in an SSH certificate infrastructure. Every server's TrustedUserCAKeys and every client's @cert-authority entry must be updated. There is no chain-of-trust mechanism in OpenSSH certificates — unlike X.509, there are no intermediate CAs.
The recommended approach for CA rotation is to run both old and new CA keys in parallel during a transition period:
# sshd_config during rotation — trust both CAs
TrustedUserCAKeys /etc/ssh/ca_user_key_old.pub /etc/ssh/ca_user_key_new.pub
# known_hosts during rotation — trust both CAs for hosts
@cert-authority *.example.com ssh-ed25519 AAAA...OLD_CA_KEY...
@cert-authority *.example.com ssh-ed25519 AAAA...NEW_CA_KEY...
Gradually issue new certificates signed by the new CA, and once all outstanding old certificates have expired, remove the old CA key from the trust configuration. If all certificates are short-lived (minutes to hours), this rotation can complete in a single day.
For the CA key itself, production environments should protect it with a hardware security module (HSM) or a cloud KMS. The key never exists in plaintext on disk. Signing operations happen through the HSM API, and access is controlled by IAM policies with multi-party approval for sensitive operations.
Comparison: SSH Certificates vs. mTLS Client Certificates
Both SSH certificates and mTLS client certificates solve the same fundamental problem — mutual authentication — but their designs reflect very different ecosystems.
| Property | SSH Certificates | X.509 (mTLS) |
|---|---|---|
| Format | OpenSSH binary format | ASN.1 DER / PEM |
| CA hierarchy | Flat (no intermediate CAs) | Hierarchical (root → intermediate → leaf) |
| Revocation | KRL (binary, compact) | CRL / OCSP / Certificate Transparency |
| Identity binding | Principals (username, role) | Subject, SAN (DNS, IP, URI) |
| Typical validity | Minutes to hours | Days to years |
| Constraints | Critical options, extensions | Key Usage, Extended Key Usage, constraints extensions |
| Tooling complexity | ssh-keygen only | openssl, cfssl, step-ca, Vault PKI |
The flat CA model is both SSH certificates' greatest strength and their main limitation. Simplicity means less operational overhead, but it also means you cannot delegate signing authority to teams or regions via intermediate CAs. Organizations that need hierarchical delegation often layer their own signing service on top (like BLESS) that controls who can request certificates and with what principals.
Practical Deployment: Putting It All Together
A complete SSH certificate deployment typically involves four components: a user CA, a host CA, a signing service, and configuration management.
1. Generate Separate CA Keys
# User CA
ssh-keygen -t ed25519 -f /secure/user_ca -C "User CA"
# Host CA
ssh-keygen -t ed25519 -f /secure/host_ca -C "Host CA"
2. Sign All Host Keys and Configure sshd
# On each server (or via configuration management):
ssh-keygen -s /secure/host_ca \
-I "$(hostname)-$(date +%Y%m%d)" \
-h \
-n "$(hostname),$(hostname -f)" \
-V +52w \
/etc/ssh/ssh_host_ed25519_key.pub
# sshd_config additions:
HostCertificate /etc/ssh/ssh_host_ed25519_key-cert.pub
TrustedUserCAKeys /etc/ssh/user_ca.pub
AuthorizedPrincipalsFile /etc/ssh/auth_principals/%u
3. Distribute CA Trust to Clients
# System-wide known_hosts or per-user:
@cert-authority *.example.com ssh-ed25519 AAAA...HOST_CA_PUB...
4. Build a Signing Service
For manual setups, a script wrapping ssh-keygen -s works. For production environments, use a dedicated service like BLESS, step-ca, Teleport, or HashiCorp Vault's SSH secrets engine. The signing service should:
- Authenticate the requesting user through the corporate IdP (SAML/OIDC)
- Require MFA for certificate issuance
- Set short validity periods (5–30 minutes for interactive sessions, up to 8 hours for long-running jobs)
- Assign principals based on the user's IdP group memberships
- Log every issuance event for audit purposes
- Store the CA key in an HSM or cloud KMS
OpenSSH Certificate Types in the Wire Protocol
At the protocol level, SSH certificates are identified by key type strings with the [email protected] suffix. When a client authenticates with a certificate, it sends a SSH_MSG_USERAUTH_REQUEST with the certificate as the public key and the algorithm set to the certificate type:
# Certificate key types:
[email protected]
[email protected]
[email protected]
[email protected]
[email protected] # FIDO2 security keys
[email protected]
The server verifies the certificate by: (1) checking the certificate type and version, (2) verifying the CA signature against its configured TrustedUserCAKeys, (3) checking validity times against the current clock, (4) checking that the requested username matches one of the certificate's principals (or the AuthorizedPrincipalsFile), (5) checking the KRL if configured, and (6) enforcing critical options. Only if all checks pass is authentication successful.
Integration with FIDO2 and Hardware Security Keys
OpenSSH 8.2+ supports FIDO2/WebAuthn security keys, and these can be combined with certificates. The security key generates a key pair where the private key never leaves the hardware token. The CA signs the public key into a certificate, and authentication requires both the valid certificate and physical presence (touching the security key).
# Generate a FIDO2-backed key
ssh-keygen -t ed25519-sk -f ~/.ssh/id_ed25519_sk
# Sign it into a certificate
ssh-keygen -s ca_key \
-I "alice-fido-20260424" \
-n alice \
-V +8h \
~/.ssh/id_ed25519_sk.pub
This achieves multi-factor authentication at the protocol level: the certificate proves the user's identity was verified by the CA (something the organization knows), and the FIDO2 key proves physical possession (something the user has). The touch requirement ensures that even malware on the user's machine cannot silently use the key — every authentication requires a deliberate physical action.
Common Pitfalls
Several failure modes recur in SSH certificate deployments:
- Clock skew: Certificates are time-bound. If a server's clock is off by more than a few minutes, it will reject valid certificates (or accept expired ones). NTP synchronization is not optional in a certificate-based environment.
- Empty principals: A certificate with no principals is valid for any username. Never issue certificates without explicit principals unless you intentionally want a superuser credential.
- CA key on disk: The CA key is the root of trust for the entire infrastructure. Storing it as a plaintext file on an engineer's laptop is a single point of compromise. Use HSMs, KMS, or ephemeral compute (Lambda) to protect signing operations.
- Forgetting
AuthorizedPrincipalsFile: Without it, any user with a certificate containing the right principal can log in as any local account that matches. The principals file lets servers control which principals map to which local accounts. - Not separating user and host CAs: A compromised user CA should not let attackers impersonate hosts. Use separate key pairs.
- Overly long validity periods: A 1-year certificate issued to an employee who leaves after 6 months is a 6-month vulnerability window. Short validity periods are the primary control; KRLs are the fallback.
Beyond OpenSSH: Teleport, Vault, and step-ca
Several production-grade systems build on OpenSSH's certificate primitive:
Teleport (by Gravitational) is a full access plane that uses SSH certificates under the hood. It adds session recording, RBAC, per-session MFA, and an audit log. Users authenticate through a web UI or CLI, receive a short-lived certificate, and connect to servers registered with the Teleport cluster. Teleport also handles host certificates automatically when servers join the cluster.
HashiCorp Vault's SSH secrets engine can sign SSH certificates on demand. Engineers authenticate to Vault (using LDAP, OIDC, or other auth backends), request a signed certificate with specific principals, and receive a certificate scoped to their Vault policy. Vault handles CA key storage, access control, and audit logging.
step-ca (by Smallstep) is a lightweight open-source CA that supports both X.509 and SSH certificates. It provides OIDC-based authentication, automatic certificate renewal, and integration with cloud identity providers. It is less opinionated than Teleport and lighter weight than Vault, making it a good fit for organizations that want certificate infrastructure without a full access management platform.
All three systems address the same fundamental gap in OpenSSH: the ssh-keygen -s command is a signing tool, not an issuance service. Production deployments need authentication, authorization, auditing, and automation around that signing operation, and these tools provide it.
Further Reading
SSH certificates build on the fundamentals of SSH key exchange and authentication. The trust model shares principles with TLS certificate validation and mutual TLS, though the format and mechanics are entirely different. For the X.509 side of certificate auditing, see Certificate Transparency. Understanding how man-in-the-middle attacks exploit TOFU makes clear why host certificates matter. Try looking up your own network's SSH infrastructure using the BGP Looking Glass to see how your servers are connected to the internet.