Skip to content
SSHSSH key pair

How to rotate SSH keys when someone leaves

When someone leaves, remove their own SSH keys and reissue any shared key they could read: a new pair next to the old one, switch clients, revoke the old key.

SSHSSH key pair

Where to issue a new one
man.openbsd.org/ssh-keygen.1
What a leaked key reaches
Every account on every host whose authorized_keys lists the public key, with that account's rights, unless key options restrict it.
Can be scoped
Partly

Some key types can be limited, others carry full rights.

Expires
None for plain keys; expiry-time= or a certificate validity adds one
Switching keys
Overlap windowRevokes at once

What Crunu does here

Crunu doesn't reissue keys at providers. It shows which values to replace and keeps the new one.

Build the list for a departure with the offboarding checklist
On this page

Someone leaving means two separate SSH jobs. Their own key needs no reissue at all: the private half was theirs, so remove their public key from every authorized_keys file and every service that trusts it. A shared key whose private half they could read, such as a deploy key or a team's server key, needs a real reissue: generate a new pair, add the new public key next to the old one on every host, move each client to the new private key, and then delete the old public key. An authorized_keys file holds one key per line, so both keys work during the switch and nobody gets locked out (sshd manual).

Crunu doesn't log in to your servers or edit authorized_keys. It keeps shared private keys as files in an environment, shows who received which version, and writes the new key to the machines that need it.

Where a public key is trusted

sshd reads public keys from the files named by AuthorizedKeysFile, by default .ssh/authorized_keys and .ssh/authorized_keys2 in the user's home directory (sshd_config). Every line is one key with an optional options field in front. The options are your scope controls, and a reissue is a good time to add them:

  • from= limits the key to hosts or addresses, including CIDR ranges.
  • command= forces one command whenever the key is used and ignores what the client asked for.
  • restrict turns off port, agent and X11 forwarding, PTY allocation and ~/.ssh/rc, and will include future restrictions too.
  • expiry-time= sets a date after which the key is no longer accepted.

Servers aren't the only places. Code hosting stores deploy keys, cloud providers inject keys into new machines, and removing a person from a cloud team doesn't necessarily remove their keys from machines already running. The pages on GitHub deploy keys and DigitalOcean cover two common cases.

Reissue a shared key without downtime

  1. Generate a new pair with ssh-keygen. Without arguments it creates an Ed25519 key; the comment set with -C helps you find the key on servers later.
  2. Add the private key as a file in the Crunu environment that held the old one, for example payments-api / production, with a path outside ~/.ssh/ such as deploy/deploy_ed25519, and save. The new version is recorded with its author and time.
  3. Append the new public key to authorized_keys on every host and account that trusted the old one. Leave the old line in place for now.
  4. Move each client to the new private key: run crunu pull on the CI runner or server that connects, then test a login with the new key.
  5. Delete the old public key's line from every authorized_keys file. For extra certainty, list it in the server's revoked keys file as well.
Shell
ssh-keygen -t ed25519 -C "deploy payments-api 2026-10" -f ./deploy_ed25519
crunu pull --env payments-api/production
ssh -i ~/deploy/deploy_ed25519 deploy@api-prod-1 true

crunu pull writes files with mode 0600 and refuses paths inside ~/.ssh/ unless you pass --allow-path for that run, so either keep deploy keys outside ~/.ssh/, as in this example, or allow the path explicitly.

Make the removal stick

Deleting a line works until someone pastes it back. RevokedKeys in sshd_config names a file of keys that sshd always refuses, written either as plain public keys, one per line, or as a key revocation list made with ssh-keygen -k. Two warnings from the manual: if that file isn't readable, public key authentication is refused for every user, and because sshd may read it on each attempt, replace it atomically rather than editing it in place.

Shell
ssh-keygen -k -f /etc/ssh/revoked_keys.krl ./old_deploy_ed25519.pub

Removing a key affects the next authentication. A session that is already open was authenticated before the change, so end the departing person's open sessions separately.

Keys that expire on their own

Plain key pairs never expire, which is why old keys pile up. OpenSSH certificates are the alternative. A host trusts a certificate authority listed in TrustedUserCAKeys, and you sign each user's key with ssh-keygen -s, using -V to set a validity interval (ssh-keygen). Outside that interval the certificate isn't valid, so a departure ends at the next expiry even if nobody edits a single server. Without -V, a certificate is valid from the Unix epoch to the distant future, and a certificate without principals isn't accepted through TrustedUserCAKeys.

When to reissue SSH keys

  • A departure. Remove their personal keys everywhere, reissue every shared key they could read, and end their sessions. In Crunu, removing the person produces a reissue checklist of the files they received, such as a deploy key, by path.
  • A leak. Remove the old public key and add it to the revoked keys file first, then distribute the new pair.
  • A schedule. Shared keys have no built-in end date, so reissue them on a calendar or move to certificates.

What to rotate when a developer leaves puts SSH in the context of the whole departure, and the offboarding checklist builds the list for your stack.

Frequently asked questions

Something else? Write to support@crunu.com

Do I need to reissue a departing person's own SSH key?

No. Their private key was always theirs, so there is nothing to replace. Remove their public key from every authorized_keys file and every service that trusts it. Reissue only shared keys whose private half they could read.

Can two SSH keys work for the same account at the same time?

Yes. Each line of an authorized_keys file holds one key, so you add the new public key next to the old one, move every client to the new private key, and then delete the old line.

Do SSH keys expire?

A plain key pair never expires. The expiry-time option in authorized_keys sets a time after which sshd stops accepting a key, and certificates signed with ssh-keygen can carry a validity interval. By default a certificate is valid forever.

Does Crunu remove keys from authorized_keys on my servers?

No. Crunu doesn't manage server logins. It stores shared private keys as environment files, shows who received them, and writes the new key to machines with crunu pull. Editing authorized_keys on each host is your step.

Sources

Every source this page relies on, with the date we last checked it.

  1. OpenBSD manual: sshd(8), AUTHORIZED_KEYS FILE FORMAT · checked October 30, 2026
  2. OpenBSD manual: sshd_config(5) · checked October 30, 2026
  3. OpenBSD manual: ssh-keygen(1) · checked October 30, 2026
  • Stripe

    Secret API key

    Expiry: Until you expire or rotate it

    sk_live_…Partly scoped

  • Vercel

    Access token

    Expiry: Chosen at creation; the token stops working after that date

    vcp_…Scoped keys

  • Anthropic

    Claude API key

    Expiry: Chosen at creation: 3 hours to 30 days, custom or Never; fixed afterwards

    sk-ant-api…Partly scoped

  • AWS

    IAM user access key

    Expiry: None: a long-term credential until you deactivate or delete it

    AKIA…Partly scopedDelayed revocation

  • Bitbucket

    API token with scopes

    Expiry: 1 to 365 days; one year by default

    Scoped keys

  • Cloudflare

    API token

    Expiry: Optional TTL chosen when the token is created

    cfut_…Scoped keys

Know which values to replace

Free during early access. When someone leaves, Crunu lists the values they received, changes the environment keys, and keeps each new value as a new version.