Skip to content

Your SSH Keys, Your Machine: Local Generation, Passphrases, Backups, and Recovery ​

Most SSH key advice is written from the server's side: what to put in authorized_keys, how to revoke an offboarded employee's access, how to audit which fingerprint logged in where. That is a fleet problem, and this article is not that article. This one stays on the client side — the private key file on your laptop, the passphrase that protects it, the backup copy in your archive, the second device, and what you actually do in the hour you realize the key is gone or has leaked.

The two halves fail differently, which is why the split matters. On the server side you revoke a public key you keep a record of. On the client side you are the only person who ever sees the private key, so nobody else can revoke it for you. Because your client will offer that key to a host without asking you anything, a private key file that leaves your control — a stolen laptop, a synced folder you forgot about, one git commit from the wrong directory — is already a working credential in someone else's hands.

The private key is the only secret you hold ​

A normal OpenSSH client keeps everything in ~/.ssh. The private key must be 600: if it is group- or world-readable, the client refuses it with Permissions 0644 for '...' are too open and the connection falls back to whatever else it can find. The public key is not a secret and is normally 644:

bash
$ stat -c '%a %n' /tmp/demo_key_rotation /tmp/demo_key_rotation.pub
600 /tmp/demo_key_rotation
644 /tmp/demo_key_rotation.pub

After permissions, the next thing to know is which files your client will offer. ssh -G prints the effective configuration with every default and ~/.ssh/config entry already merged, which is the honest way to see what the next connection will try:

bash
$ ssh -G github.com | grep -E '^(identityfile|identitiesonly)'
identitiesonly no
identityfile ~/.ssh/id_rsa
identityfile ~/.ssh/id_ecdsa
identityfile ~/.ssh/id_ecdsa_sk
identityfile ~/.ssh/id_ed25519
identityfile ~/.ssh/id_ed25519_sk
identityfile ~/.ssh/id_xmss
identityfile ~/.ssh/id_dsa

identitiesonly no is the default, so every one of those default-named keys may be offered to every host you connect to. That is what makes a single leaked key named id_rsa or id_ed25519 more dangerous than it looks, and it is why per-host IdentityFile plus IdentitiesOnly yes is worth the two lines of config:

ssh-config
Host myserver
    HostName 192.168.1.100
    Port 22
    User deploy
    IdentityFile ~/.ssh/id_ed25519
    IdentitiesOnly yes
    ServerAliveInterval 60
    ServerAliveCountMax 3

Graphical clients make their own choice about where a saved password, private key body, or passphrase ends up. That location should be inspectable, and on a desktop it is normally somewhere under the app's data directory — see data storage paths for the concrete folders on Windows and macOS.

Generating a key pair locally: ed25519 first ​

Algorithm choice is the first decision, and it happens entirely on your machine. RSA 2048-bit is still considered secure at current compute levels, but it produces larger keys and slower generation. Ed25519 gets equivalent or stronger security out of 256 bits with better performance. OpenSSH 6.5 and later support ed25519, and modern distributions ship 7.x or newer, so compatibility is rarely the constraint.

bash
# Generate an ed25519 key pair locally, with a comment identifying owner and purpose
ssh-keygen -t ed25519 -C "yuki@laptop-2026" -f ~/.ssh/id_ed25519

# For legacy systems that cannot do ed25519, generate RSA 4096-bit
ssh-keygen -t rsa -b 4096 -C "yuki@laptop-legacy" -f ~/.ssh/id_rsa_4096

# Print the fingerprint — this is the value you compare against the server or console
ssh-keygen -lf ~/.ssh/id_ed25519.pub

Real output of that last command on a freshly generated pair:

text
256 SHA256:EozutPvJRbMlBsZDSQ55p8GH/lusZ8jLZOIzpUJLCq8 yuki@laptop (ED25519)

The -C comment is the only label you will have later. When the same public key has sat in six different authorized_keys files for a year, the comment is what tells you which of your keys it is — without it, identification means running ssh-keygen -lf against every file and comparing fingerprints by eye. Write the owner, the machine, and the year into it when you generate the key, not afterwards.

Passphrases and ssh-agent: protecting the file, not the server ​

A passphrase on the private key is the cheapest protection available, and it is the one most often skipped. An unprotected key is usable the second the file leaks. A passphrase-protected key means the file alone is not enough: an attacker also needs the passphrase, which is why the passphrase must not be written next to the key or inside the same synced folder.

The usual objection is friction, and that is what ssh-agent removes. The passphrase is entered once, the decrypted key stays in the agent's memory, and later connections reuse it:

bash
# Start an agent and load a key (prompts for the passphrase once)
eval "$(ssh-agent -s)"
ssh-add ~/.ssh/id_ed25519

# List what is loaded
ssh-add -l

# Load with an expiry so the passphrase is needed again after 8 hours
ssh-add -t 28800 ~/.ssh/id_ed25519

# Remove a key from the agent when you step away
ssh-add -d ~/.ssh/id_ed25519

Two client-side details matter here. First, anything that can read the agent's memory or talk to its socket can use the key without knowing the passphrase — so do not keep a high-privilege key loaded indefinitely on a machine other people can log into, and prefer ssh-add -t with an expiry plus an explicit ssh-add -d when you are done. Second, in automation, point the agent socket at a dedicated path with IdentityAgent instead of hardcoding a plaintext passphrase in a script, which would turn a protected key into an unprotected one in one line.

Backups: what to archive and where it may live ​

The private key is the one file in ~/.ssh that cannot be regenerated. Losing it costs a rotation on every host that trusts the matching public key; losing a laptop with no backup is therefore an access-management incident, not just a hardware replacement.

What is worth archiving: the private key, the matching .pub (so the two never get separated), and ~/.ssh/config, which holds the host aliases, usernames, and IdentityFile lines that make the keys useful. known_hosts is optional and rebuilds itself.

How to store it: encrypt the archive before it goes anywhere, and keep at least one copy that no sync client touches.

bash
# Encrypt an SSH key archive with a passphrase (age prompts for it)
tar -czf - ~/.ssh/id_ed25519 ~/.ssh/id_ed25519.pub ~/.ssh/config | age -p -o ssh-keys-2026.tar.gz.age

# Restore on a new machine, then fix permissions
age -d -o - ssh-keys-2026.tar.gz.age | tar -xzf - -C ~/
chmod 600 ~/.ssh/id_ed25519 && chmod 644 ~/.ssh/id_ed25519.pub

The rules that keep a backup from becoming the leak: no private key in a plain cloud drive, a notes app, a password field labeled "server", or a repository — public or private. Encrypted sharing channels are fine for moving a key between two of your own machines, but an archive that is protected only by a cloud account password is protected by exactly the thing most likely to be phished. Test the restore once on a scratch machine before you need it, because an archive you have never decrypted is a hypothesis, not a backup. If you sync client settings between devices with a hosted channel, check how that channel treats secrets — Termark's own model is described in cloud sync.

Multi-device: one key per device beats one key everywhere ​

The convenient move is to copy ~/.ssh/id_ed25519 to your desktop, your laptop, and the work VM. The consequence is that all three are now the same credential: a leak on any one of them is a leak everywhere, and revoking it means touching the other two. Per-device keys cost one extra ssh-keygen and one extra public key line per host, and they buy you the ability to cut off a single device after it is lost without rotating anything else.

If you do copy a key — a temporary rescue laptop, a replacement machine mid-incident — move it over an encrypted channel, delete it from the intermediate storage afterwards, and treat any machine you copied it to as a device that now needs its own key. The same reasoning applies to the settings around the key: syncing host lists, groups, and connection profiles between devices is useful and low-risk; syncing private keys is not.

Where a client stores your credentials ​

If a graphical SSH client stores passwords, private keys, or passphrases for you, its marketing adjectives are not the answer to the question that matters. Four questions are:

  1. Are the sensitive fields written to disk in plaintext?
  2. Where does the key that decrypts them live?
  3. Is data encrypted before it is uploaded anywhere?
  4. Can the service side decrypt your data, or restore it for you?

Sensitive data here means SSH passwords and private key material, key passphrases, jump-host and proxy credentials, the sync password, API keys. Host addresses, asset names, and group structure are a second tier — they leak the shape of your internal network even when the credentials themselves are safe.

Local encryption has to keep ciphertext and key apart. If a client encrypts its database and then stores the decryption key in the same ordinary config directory, anyone who copies that directory can still decrypt it at low cost. The reasonable arrangement for an installed build is: ciphertext in the local database, and the local data key held or protected by the operating system's secure storage (Keychain on macOS, the credential-protection facilities on Windows). That does not survive a fully compromised device, but it does remove the "copy one file, get every credential" failure mode. Termark's version of this boundary is documented in local encryption and data recovery.

Portable builds need their own design. A Windows portable edition is meant to be carried data directory and all. If it depends entirely on the original machine's secure storage, it cannot unlock after a move; if it drops encryption for portability, that is worse. The clear boundary is a local encryption passphrase: the client derives a local key from the passphrase and a random salt, stores ciphertext plus the salt and KDF parameters, stores neither the passphrase nor a plaintext data key, and asks for the passphrase again after migration. It is one extra step, but "whoever holds the passphrase can decrypt" is a rule a user can actually reason about.

Sync data should be encrypted before upload. Multi-device sync means the data leaves your machine, so the follow-up questions are: is the ciphertext or the plaintext uploaded, is the sync password sent to the storage side, does the provider hold the ability to decrypt, and does the same treatment apply to WebDAV, S3, or iCloud channels. When encryption happens client-side, the server only stores and transports, and never touches the plaintext of your server credentials. The direct price is that a lost sync password is usually unrecoverable — including by the vendor.

"We can restore all your data" conflicts with end-to-end encryption. Resetting a password on a normal website is routine; a product that claims both "the server cannot decrypt your synced data" and "support can recover every server credential if you forget the sync password" is describing two different decryption paths, and the second one is worth asking about directly. For server credentials, "no recovery after a forgotten sync password" is inconvenient but is what a real security boundary looks like; the product should say so when sync is enabled and tell you to keep your own recovery information.

Sync conflicts are a reliability problem too. A desktop and a laptop can modify the same assets at once. Silent last-write-wins can lose newly added assets or command snippets, roll a credential back to an older value, and replace current port-forwarding or jump-host configuration with stale data. On a detected conflict the user should at least be able to choose between the remote and local version.

AI features add one more layer of questions. An AI-assisted client may read terminal output, config fragments, or logs. Beyond local credentials, ask which terminal content is sent to the model, whether you can bound how much recent output is included, where the API key is kept, whether you can point it at your own endpoint, and whether generated commands that change remote state run without confirmation. A short client checklist:

  • [ ] Passwords, private keys, and proxy credentials are not stored in plaintext
  • [ ] The local data key is not sitting in the same plain file as the ciphertext
  • [ ] A portable build has a defined migration and unlock path
  • [ ] Sync data is encrypted client-side before upload
  • [ ] The sync password is not sent to the storage service
  • [ ] The product states whether the server side can decrypt
  • [ ] The result of forgetting the sync password is stated up front
  • [ ] Sync conflicts are not resolved by silent overwrite
  • [ ] AI context scope is understandable and controllable
  • [ ] Commands that can change remote state are visible before they run

These boundaries do not replace device security, least privilege on the server, or the rotation and backup habits in the rest of this article. No local client can promise absolute safety on a device that is already fully controlled.

Lost or leaked: the first hour, and the rotation sequence ​

Two situations look similar and are not. A lost key — a dead disk, a wiped laptop, a key you simply cannot find — is an availability problem: you still hold the credential's authority, you just cannot use it. A leaked key — stolen device, shared screen, committed file, synced folder — is a compromise: assume it is already usable by someone else, and act in that order of urgency.

For a lost key, restore from the encrypted backup first if you have one; while you wait for that, check whether the client's agent still holds the key (ssh-add -l) and whether a second device still has it, because a working copy changes the urgency. If no copy exists, you are rotating: generate a new pair and deploy it everywhere the old public key was trusted.

For a leaked key, the hour looks like this:

bash
# 1. See what is still loaded in the agent, and drop it
ssh-add -l
ssh-add -D

# 2. Generate the replacement locally
ssh-keygen -t ed25519 -C "yuki@laptop-rotation-$(date +%Y%m%d)" -f ~/.ssh/id_ed25519_new

# 3. Deploy the new public key to each host that trusted the old one
ssh-copy-id -i ~/.ssh/id_ed25519_new.pub deploy@target-server

# 4. Verify the new key before revoking anything, without closing the current session
ssh -i ~/.ssh/id_ed25519_new deploy@target-server 'echo "new key works"'

# 5. Only then remove the old public key from the server, and record the change
ssh deploy@target-server 'grep -n "old key comment" ~/.ssh/authorized_keys'

# 6. Promote the new key locally and retire the old files
mv ~/.ssh/id_ed25519 ~/.ssh/id_ed25519.bak.$(date +%Y%m%d)
mv ~/.ssh/id_ed25519_new ~/.ssh/id_ed25519
mv ~/.ssh/id_ed25519_new.pub ~/.ssh/id_ed25519.pub
chmod 600 ~/.ssh/id_ed25519

Steps 3 and 4 must happen before step 5, and the current session must stay open while you do it. Revoking the old key before the new one is verified locks you out of the very machine you are rotating. Meanwhile, handle the places that are not shell servers: a public key uploaded to a Git host, a deploy key, a CI secret, or a jump-host configuration can be replaced or disabled from a web console while the server-side rotation is still running, and doing that early shrinks the window. Where a host supports a second factor, requiring it is a reasonable complement to a key you are no longer fully sure about — automatic OTP interactive auth covers the keyboard-interactive prompt path in Termark.

One thing the client side cannot do alone: if the same key was trusted on servers managed by someone else, or by a team that keeps its own authorized_keys records, they need to be told. Key changes belong in change management, and a leaked key is worth a note to whoever owns the hosts.

Rotating every copy, including the ones people forget ​

Rotating is not finished when the new key logs in. The old credential has to be gone from everywhere it existed, and most of the work is remembering where that is. A realistic inventory for a key you generated two years ago:

  • ~/.ssh/id_ed25519 (and any renamed or old copies in the same directory)
  • the currently loaded agent: ssh-add -l, then ssh-add -D
  • the encrypted backup archive, which must be replaced, not left alongside the new one
  • any sync folder or cloud drive the archive passed through
  • Git hosts and CI systems holding the matching public key or deploy key
  • the IdentityFile line in ~/.ssh/config on each machine, if the filename changed
  • a jump host or bastion's own configuration
  • the previous machine, if it was not wiped

After the key is in place, confirm identity from the fingerprint rather than the filename, and give the new comment a date so the next rotation can find it:

bash
$ ssh-keygen -lf ~/.ssh/id_ed25519.pub
256 SHA256:EozutPvJRbMlBsZDSQ55p8GH/lusZ8jLZOIzpUJLCq8 yuki@laptop (ED25519)

A quarterly pass over your own machine takes a few minutes and catches the drift: ssh-keygen -lf every .pub in ~/.ssh, check ssh -G <host> for keys you did not expect to be offered, look for a .bak copy of a key you thought you deleted, and confirm the newest backup archive can still be decrypted. Keys that live on one machine, with one passphrase, one archived copy, and a comment naming the device are the ones that do not turn into a surprise during an incident.

Once the keys on your own machine are in order, the next question is where they are used. Termark keeps hosts, keys, snippets, and port forwards in one workspace so the copies you handed out are easier to trace: https://www.termark.app.

References ​

Termark · SSH client and terminal workspace