Skip to content

Generating SSH Keys the Right Way: ssh-keygen Options That Matter

The ssh-keygen flags worth knowing: KDF rounds, comments, per-purpose key files, FIDO2 hardware keys, passphrase rotation, fingerprints and restricted automation keys.

●By 10 min read

Most people generate an SSH key once, by pressing Enter three times, and then use that single key for every server, every Git host and every automation job for a decade. It works, which is exactly why nobody revisits it. But the defaults you accept at that prompt decide how long a stolen laptop backup stays safe, whether you can tell your keys apart in an authorized_keys file, and how much damage a leaked CI key can do.

This guide is about the flags: what ssh-keygen actually does with -a, -C, -f, -O and friends, and how to manage keys after they exist. For server-side settings such as disabling password logins, see our SSH security best practices.

A Better Default Command

OpenSSH 9.5 and later generate Ed25519 keys when you do not pass -t, and since 7.8 every private key is written in the OpenSSH format, so the old -o flag no longer changes anything. What the defaults still get wrong is the file name, the comment and the passphrase work factor. This is the command worth memorising:

$ ssh-keygen -t ed25519 -a 64 -C "alice@thinkpad work 2026-10" -f ~/.ssh/id_ed25519_work

Generating public/private ed25519 key pair.

Enter passphrase (empty for no passphrase):

Enter same passphrase again:

Your identification has been saved in /home/alice/.ssh/id_ed25519_work

Your public key has been saved in /home/alice/.ssh/id_ed25519_work.pub

The key fingerprint is:

SHA256:q3Vf0m1sX9wq2bK7cP0yJr8Hc1nG4tLx6eZs5uD2aYk alice@thinkpad work 2026-10

The key's randomart image is:

+--[ED25519 256]--+

| ... |

+----[SHA256]-----+

FlagWhat it controlsRecommendation
-tKey algorithmed25519 for software keys, ed25519-sk for hardware keys, rsa only for legacy servers
-abcrypt KDF rounds used to encrypt the private key with your passphrase (default 16)32-100; higher slows offline guessing and also slows each unlock
-CComment appended to the public key (default user@host)Who, which machine, what purpose, when
-fOutput fileOne file per purpose, never overwrite id_ed25519 blindly
-NPassphrase on the command lineOnly -N "" for unattended keys; never type a real passphrase here
-bKey size (RSA and ECDSA)Ignored for Ed25519; 3072 is the RSA default, 4096 if you must use RSA

Why -a matters β€” the private key file is only as strong as its encryption. With the default 16 rounds, an attacker who copies ~/.ssh from a backup can try passphrases quickly. Each extra round multiplies their cost. Time the unlock with time ssh-keygen -y -f ~/.ssh/id_ed25519_work and pick the highest value that still feels instant.

Comments are for humans β€” six months from now, a line in authorized_keys ending in alice@localhost tells you nothing. A comment like alice@thinkpad work 2026-10 tells you whose key it is, where it lives and when to rotate it.

RSA is the compatibility fallback β€” use it only for appliances or old servers that reject Ed25519. Since OpenSSH 8.8 the client disables the SHA-1 ssh-rsa signature scheme by default, so servers too old to support rsa-sha2-256 or rsa-sha2-512 can fail even with an RSA key.

One Key Per Purpose, Selected by ssh_config

A single key everywhere means a single leak compromises everything, and you cannot revoke access to one system without breaking all the others. Separate keys for work servers, personal Git hosting and each automation job cost nothing. The trick is to make SSH pick the right one automatically:

# ~/.ssh/config

Host github.com

User git

IdentityFile ~/.ssh/id_ed25519_github

IdentitiesOnly yes

Host *.corp.example.com bastion-*

User alice

IdentityFile ~/.ssh/id_ed25519_work

IdentitiesOnly yes

AddKeysToAgent 8h

Host *

AddKeysToAgent no

IdentitiesOnly yes β€” without it, SSH also offers every key loaded in your agent. Servers count each offered key as an authentication attempt, and with five or six keys you hit MaxAuthTries and get "Too many authentication failures" before the right key is tried.

AddKeysToAgent with a lifetime β€” the first connection asks for the passphrase and caches the key for eight hours. ssh-add -l lists what is loaded; ssh-add -D clears it before you walk away.

Confirm every use β€” ssh-add -c ~/.ssh/id_ed25519_work makes the agent ask for confirmation through ssh-askpass each time the key signs. That protects you when forwarding the agent to a host you only partly trust.

Generate the config β€” our SSH config generator builds Host blocks like these, including ProxyJump for bastions.

Install the public half with ssh-copy-id -i ~/.ssh/id_ed25519_work.pub [email protected]. The -i matters: without it, ssh-copy-id copies every public key it can find.

Hardware-Backed Keys With FIDO2

Since OpenSSH 8.2, a FIDO2 security key can hold the secret part of an SSH key. The file in ~/.ssh becomes a handle that is useless without the physical token, and every signature needs a touch. This is the single biggest upgrade available for keys that guard production access.

# Non-resident: the handle file lives on this machine only

$ ssh-keygen -t ed25519-sk -C "alice yubikey-5c work" -f ~/.ssh/id_ed25519_sk_work

# Resident + PIN: the key lives on the token and requires PIN and touch

$ ssh-keygen -t ed25519-sk -O resident -O verify-required \

-O application=ssh:work -C "alice yubikey-5c work" -f ~/.ssh/id_ed25519_sk_work

# On a new laptop, pull resident key handles back off the token

$ cd ~/.ssh && ssh-keygen -K

Enter PIN for authenticator:

Saved ED25519-SK key ssh:work to id_ed25519_sk_rk_work

-O optionEffect
residentStores the key on the token so it can be recovered with ssh-keygen -K on another machine
verify-requiredRequires the token PIN (user verification) in addition to touch, OpenSSH 8.4+
application=ssh:nameLabels the credential so several resident SSH keys can coexist on one token
no-touch-requiredSkips the touch; the server must also allow it with the no-touch-required authorized_keys option

Server support β€” the server needs OpenSSH 8.2 or later to accept sk- key types. Older appliances and some managed Git hosts reject them, so keep a software key for those.

Middleware β€” on Linux, OpenSSH talks to the token through libfido2. On Debian and Ubuntu that is the libfido2-1 package; on Fedora and RHEL, libfido2. If ssh-keygen says the key type is unsupported, the client was built without FIDO support.

ecdsa-sk β€” use it only when a token does not support Ed25519. Most current FIDO2 tokens support both.

Register two tokens β€” a hardware key you can lose needs a backup. Enroll a second token and add both public keys to the servers that matter.

sponsored

Managing Keys After They Exist

Most key hygiene happens long after generation: adding a passphrase you skipped, raising the KDF rounds, checking which fingerprint a server logged, or recovering a lost .pub file. All of it is ssh-keygen:

# Add, change or remove the passphrase; -a re-encrypts with more rounds

$ ssh-keygen -p -a 64 -f ~/.ssh/id_ed25519_work

# Change the comment stored in the private key

$ ssh-keygen -c -C "alice@thinkpad work 2026-10" -f ~/.ssh/id_ed25519_work

# Regenerate a lost public key from the private key

$ ssh-keygen -y -f ~/.ssh/id_ed25519_work > ~/.ssh/id_ed25519_work.pub

# Fingerprint, as shown in sshd logs and in GitHub's key list

$ ssh-keygen -l -f ~/.ssh/id_ed25519_work.pub

256 SHA256:q3Vf0m1sX9wq2bK7cP0yJr8Hc1nG4tLx6eZs5uD2aYk alice@thinkpad work 2026-10 (ED25519)

# Legacy MD5 format, for old consoles that still display it

$ ssh-keygen -l -E md5 -f ~/.ssh/id_ed25519_work.pub

# Fingerprint every key in an authorized_keys file

$ ssh-keygen -l -f ~/.ssh/authorized_keys

# Which key logged in? sshd records the fingerprint

$ sudo journalctl -u ssh -g "Accepted publickey" --since today

Accepted publickey for alice from 10.0.4.17 port 51022 ssh2: ED25519 SHA256:q3Vf0m1s...

Service name differs β€” the unit is ssh on Debian and Ubuntu and sshd on RHEL, Fedora and Arch. Adjust the journalctl command accordingly.

Is there a passphrase? β€” ssh-keygen -y -P "" -f key prints the public key if the private key is unencrypted and fails if it has a passphrase. Handy for auditing a fleet of automation keys.

Legacy PEM export β€” some older tools only read PEM-format RSA keys. ssh-keygen -p -m PEM -f id_rsa_legacy rewrites an RSA key in that format. Do it on a copy, not on the key you log in with.

Permissions β€” OpenSSH refuses a private key readable by others, with "UNPROTECTED PRIVATE KEY FILE". Keep ~/.ssh at 700 and private keys at 600; our chmod calculator explains the octal values.

Keys for Automation: Empty Passphrase, Narrow Scope

Backup jobs, deploy hooks and monitoring checks cannot type a passphrase, so their keys are generated with -N "". That is acceptable only when the server side limits what the key can do. OpenSSH supports this directly in authorized_keys:

$ ssh-keygen -t ed25519 -N "" -C "backup@app1 to nas1 rsync" -f ~/.ssh/id_ed25519_backup

# On nas1, in ~backup/.ssh/authorized_keys (one line):

restrict,from="10.0.4.21",command="/usr/local/bin/rrsync -ro /srv/backup" ssh-ed25519 AAAAC3Nza... backup@app1 to nas1 rsync

# Test that anything else is refused

$ ssh -i ~/.ssh/id_ed25519_backup backup@nas1 'cat /etc/shadow'

authorized_keys optionEffect
restrictDisables port, agent and X11 forwarding, PTY allocation and ~/.ssh/rc in one word
command="..."Runs only this command; the client's requested command is in $SSH_ORIGINAL_COMMAND
from="pattern"Accepts the key only from matching addresses or host names
expiry-time="20270101"Key stops working after this date (OpenSSH 7.7+)
pty, port-forwardingRe-enable a single capability after restrict

rrsync β€” a helper shipped with rsync that limits a forced command to one directory. Debian and Ubuntu install it as /usr/bin/rrsync in recent versions; on other distros it may sit under the rsync documentation directory and need copying into place.

Never reuse a human key β€” if the backup key leaks, you revoke one line on one server. If it is your personal key, you revoke your own access everywhere.

Prefer short-lived credentials at scale β€” when a fleet grows past a few dozen hosts, signing keys with an SSH certificate authority (ssh-keygen -s ca_key -I id -n principals -V +1d key.pub) replaces distributing public keys to every server. Our server hardening guide covers the sshd side.

Bonus: Sign Git Commits With the Same Tooling

Git 2.34 and later can sign commits with an SSH key instead of GPG, and ssh-keygen does the cryptography with its -Y sign and -Y verify modes. Use a dedicated key, so that revoking a signing key does not lock you out of servers:

$ ssh-keygen -t ed25519 -a 64 -C "alice commit signing" -f ~/.ssh/id_ed25519_signing

$ git config --global gpg.format ssh

$ git config --global user.signingkey ~/.ssh/id_ed25519_signing.pub

$ git config --global commit.gpgsign true

# Let git verify signatures locally

$ echo "[email protected] $(cat ~/.ssh/id_ed25519_signing.pub)" >> ~/.ssh/allowed_signers

$ git config --global gpg.ssh.allowedSignersFile ~/.ssh/allowed_signers

$ git log --show-signature -1

Upload the same public key to your Git host as a signing key, not an authentication key, and commits show as verified. The Git command builder helps with the rest of the workflow.

Frequently Asked Questions

What does ssh-keygen -a do?

It sets the number of bcrypt_pbkdf rounds used to derive the encryption key that protects your private key file with its passphrase. The default is 16. A higher value such as 64 or 100 makes offline passphrase guessing slower if the file is stolen, at the cost of a slightly slower unlock. It has no effect on keys without a passphrase.

Do I still need the -o flag with ssh-keygen?

No. Since OpenSSH 7.8 all private keys are saved in the newer OpenSSH format by default, which is what -o used to request. The flag is accepted but does nothing. Use -m PEM only when an old tool needs the legacy PEM format for an RSA key.

Should I use one SSH key for everything?

No. Use separate keys for separate trust domains, such as work servers, personal Git hosting and each automation job, and select them with IdentityFile and IdentitiesOnly in ~/.ssh/config. A leak then affects only one domain, and revoking one key does not break unrelated access.

What is the difference between ed25519 and ed25519-sk keys?

An ed25519 key keeps its secret in a file on disk. An ed25519-sk key keeps the secret inside a FIDO2 hardware token, and the file is only a handle that is useless without the token. Each login requires a touch, and optionally a PIN with -O verify-required. Both client and server need OpenSSH 8.2 or later.

How do I add a passphrase to an existing SSH key?

Run ssh-keygen -p -f ~/.ssh/your_key. It asks for the old passphrase (press Enter if there was none) and then the new one. Add -a 64 to re-encrypt with more KDF rounds at the same time. The public key and fingerprint do not change, so servers need no update.

sponsored

Related Tools

See also