SSH Key Rotation
Key Takeaways
- SSH keys have no expiry — without active rotation, a compromised key grants access indefinitely
- Rotation requires coordinated update: generate new key, deploy new public key to all servers, verify access, remove old key
- Most organizations never rotate SSH keys because the process is manual, error-prone, and risks breaking automation
- SSH certificates with short validity periods eliminate the need for manual rotation entirely
SSH key rotation is the process of replacing an existing SSH key pair with a new one and updating all systems that trust the old public key. Unlike TLS certificates (which expire and force renewal), SSH keys have no built-in expiry. A key generated in 2015 works forever unless someone actively removes it. Rotation is the only mechanism to limit the exposure window of a potentially compromised key — and it’s the step most organizations skip because it’s operationally painful.
Why it matters
- No natural expiry — SSH keys work until removed. A key compromised 3 years ago still grants access today if nobody rotated it. Rotation bounds the damage window.
- Personnel changes — when an engineer leaves, their key should be rotated (not just removed from servers they’re known to access — they may have deployed it to servers nobody tracked).
- Compliance requirement — SOC 2, ISO 27001, and NIST SP 800-57 require periodic credential rotation. SSH keys are credentials. Auditors ask: “When were these keys last rotated?”
- Algorithm upgrades — keys generated years ago may use deprecated algorithms (DSA, RSA-1024). Rotation is the mechanism to upgrade to Ed25519 or larger RSA keys.
- Incident response — after a suspected compromise, rotating all SSH keys is part of containment. If you’ve never rotated before, doing it under pressure during an incident is chaotic.
How it works
- Generate new key pair — create a new key with current best practices (Ed25519, strong passphrase)
- Deploy new public key — add the new public key to
authorized_keyson all target servers (alongside the old key temporarily) - Verify new key works — test authentication with the new key to every server
- Update automation — update CI/CD pipelines, cron jobs, and scripts to use the new private key
- Remove old public key — once all systems are confirmed working with the new key, remove the old public key from all servers
- Destroy old private key — securely delete the old private key from all locations (including backups, password managers, and any copies)
In real systems
Manual rotation script:
#!/bin/bash
# Generate new key
ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519_new -C "user@example.com $(date +%Y-%m)"
# Deploy to all servers (parallel)
SERVERS="server1 server2 server3 server4"
for host in $SERVERS; do
ssh-copy-id -i ~/.ssh/id_ed25519_new.pub user@$host
done
# Verify new key works
for host in $SERVERS; do
ssh -i ~/.ssh/id_ed25519_new user@$host "echo 'OK: $host'" || echo "FAILED: $host"
done
# After verification: remove old key from servers
OLD_KEY=$(cat ~/.ssh/id_ed25519_old.pub)
for host in $SERVERS; do
ssh -i ~/.ssh/id_ed25519_new user@$host \
"sed -i '/${OLD_KEY##* }/d' ~/.ssh/authorized_keys"
done
Ansible-managed rotation:
# Deploy authorized keys from central source of truth
- name: Manage SSH authorized keys
authorized_key:
user: "{{ item.user }}"
key: "{{ item.public_key }}"
state: present
exclusive: yes # Remove any keys not in this list
loop: "{{ ssh_authorized_keys }}"
# exclusive: yes ensures old keys are automatically removed
# Source of truth is the Ansible inventory — rotate by updating the variable
HashiCorp Vault SSH secrets engine (dynamic keys):
# Vault signs a short-lived SSH certificate — no static keys to rotate
vault write ssh-client-signer/sign/admin-role \
public_key=@~/.ssh/id_ed25519.pub \
valid_principals="ubuntu,deploy" \
ttl="8h"
# Certificate expires in 8 hours — rotation is automatic
Where it breaks
Incomplete removal — the new key is deployed and working, but the old public key isn’t removed from 3 out of 50 servers (missed in the script, server was offline, different authorized_keys path). The old key still grants access to those 3 servers indefinitely. Rotation must include verification that the old key is removed everywhere — not just that the new key works everywhere.
Automation breaks during rotation — a CI/CD pipeline uses the old private key. The old public key is removed from servers before the pipeline is updated to use the new key. Deployments fail. The team scrambles to restore the old key, breaking the rotation. Always update automation to use the new key BEFORE removing the old public key from servers. The overlap period exists for this reason.
No inventory of where keys are deployed — you want to rotate a key but don’t know which servers trust it. The public key was manually added to servers over 3 years by different team members. Without a complete inventory, you can’t guarantee the old key is removed everywhere. This is the core argument for centralized key management or SSH certificates — they eliminate the distributed authorized_keys problem entirely.
Operational insight
The reason most organizations never rotate SSH keys is that the cost of rotation (risk of breaking access, coordination effort, incomplete removal) exceeds the perceived risk of not rotating. This calculus changes instantly after an incident — but by then, the damage is done. The real solution isn’t better rotation processes — it’s eliminating the need for rotation entirely. SSH certificates with 8-24 hour validity periods achieve this: keys are effectively “rotated” every time the certificate expires. No authorized_keys to manage, no removal to coordinate, no automation to update. The certificate simply stops working after its validity period. If your organization can’t rotate SSH keys reliably, skip the rotation problem and move to certificates.
Related topics
Ready to Secure Your Enterprise?
Experience how our cryptographic solutions simplify, centralize, and automate identity management for your entire organization.