Automation, rotation, and recovery

Rotate and revoke keys

Inventory authorized identities, add and prove replacements, then remove old keys without an outage.

An SSH key in authorized_keys never expires. It stays valid until someone deletes the line. Open authorized_keys on any server that has been around for a few years and you will find keys from laptops that were recycled long ago and people who left. Nobody removed them because nobody knew what they were.

So rotation is not a command. It is a small process you run on purpose, and it starts with knowing what you have.

Inventory

On each server, list the fingerprints in the file rather than staring at base64:

ssh-keygen -lf ~/.ssh/authorized_keys
256 SHA256:9kLm3nQ7rT2vW5xY8zA1bC4dE6fG0hJ3kM7nP2qR5sU flavio notes-server operator (ED25519)
256 SHA256:pQ8wE2rT6yU1iO5pA9sD3fG7hJ2kL6zX0cV4bN8mM1q anna laptop (ED25519)
256 SHA256:zX9cV2bN5mM8qW1eR4tY7uI0oP3aS6dF9gH2jK5lZ8x ci deploy notes-server (ED25519)

Every line should map to an owner and a purpose. If a fingerprint has no comment and nobody claims it, that is your first revocation. Keep this list somewhere the team can see it. The comments in the file help, but a fingerprint is the only thing that cannot be faked or mistyped.

Add the replacement first

Say I am rotating my operator key. Generate the new one on my laptop:

ssh-keygen -t ed25519 -f ~/.ssh/notes_server_ed25519_2026 -C 'flavio notes-server operator 2026'

Append its .pub line to authorized_keys on every server that has the old one. The old key still works, so I do this using the old key:

cat ~/.ssh/notes_server_ed25519_2026.pub | ssh notes-server 'cat >> ~/.ssh/authorized_keys'

Prove it

From a new terminal, with the new key only:

ssh -o IdentitiesOnly=yes -i ~/.ssh/notes_server_ed25519_2026 [email protected] 'echo new key ok'
new key ok

Do this for every server. Not most of them. A server you forgot is a server you can no longer reach after the next step. Then update ~/.ssh/config to point IdentityFile at the new file.

Remove the old key

Delete only the line with the old fingerprint. Matching on the comment is fine when the comments are clean:

ssh notes-server "sed -i '/flavio notes-server operator$/d' ~/.ssh/authorized_keys"

Now prove the old key is dead, everywhere:

ssh -o IdentitiesOnly=yes -i ~/.ssh/notes_server_ed25519 [email protected] 'echo still works'
[email protected]: Permission denied (publickey).

That denial is the goal of the whole exercise. Run ssh-keygen -lf again and confirm the inventory shrank by exactly one line. Only then delete the old private file locally.

The order that causes outages

People get locked out by doing this backwards: remove the old key, then add the new one, from the only session they have. Or by rotating on three of four servers and finding the fourth a month later. The sequence above never has a moment where zero keys work. Add, prove, remove, prove.

Rotate the CI deploy key the same way, on a schedule you can keep. Once a year is better than never, and it also forces you to check that the inventory is still true.

Lesson completed