A FIDO2 SSH key on a YubiKey is one command:
ssh-keygen -t ed25519-skThe -sk suffix is the whole story: it stands for "security key". When you run that, OpenSSH talks to the YubiKey, the key's LED starts blinking, you tap it, and you end up with a keypair where the private half physically cannot leave the hardware. What lands in ~/.ssh is not the private key, it is a handle that is useless without the YubiKey plugged in. Steal the laptop, copy the file, and you still cannot authenticate. That is the entire point: the secret lives in a chip you can pull out of the USB port.
This needs OpenSSH 8.2 or newer on both ends and a FIDO2 key. I cover the version traps below, because they are where people actually get stuck.
Generate the key
Plug in the YubiKey first, then run:
ssh-keygen -t ed25519-sk -C "you@example.com"A real run looks like this:
Generating public/private ed25519-sk key pair.
You may need to touch your authenticator to authorize key generation.
Enter file in which to save the key (/home/techearl/.ssh/id_ed25519_sk):
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /home/techearl/.ssh/id_ed25519_sk
Your public key has been saved in /home/techearl/.ssh/id_ed25519_sk.pub
Between "touch your authenticator" and the saved paths, the YubiKey blinks and waits for a physical tap. No tap, no key. That tap is user presence: proof a human is at the machine, not a script.
You now have two files, but they are not what ssh-keygen normally produces. id_ed25519_sk is a key handle plus metadata, not a usable private key on its own. id_ed25519_sk.pub is the public key you install on servers, exactly like any other public key. The crucial difference from a software key: copying id_ed25519_sk to another machine gets an attacker nothing, because the actual private scalar never left the YubiKey's secure element.
Run ssh-keygen as your normal user. It writes to your own ~/.ssh, so no sudo. (Using sudo here makes the handle root-owned and unreadable to you, same mistake as with a regular ssh-keygen run.)
ed25519-sk or ecdsa-sk?
Two algorithms carry the -sk suffix. Pick ed25519-sk if your YubiKey supports it, fall back to ecdsa-sk if it does not.
ssh-keygen -t ed25519-sk # preferred
ssh-keygen -t ecdsa-sk # fallback for older keysThe catch is firmware. ed25519-sk needs a YubiKey on firmware 5.2.3 or later. Anything older only does ecdsa-sk. Check your firmware with:
ykman infoIf ssh-keygen -t ed25519-sk returns Key enrollment failed: requested feature not supported, that is almost always old firmware, and ecdsa-sk will work instead. YubiKey firmware is burned in at the factory and cannot be upgraded, so an older key is stuck on ecdsa-sk for life. That is not a real security problem; it is just a less modern curve.
| Algorithm | Curve | YubiKey firmware | When to pick it |
|---|---|---|---|
ed25519-sk | Ed25519 | 5.2.3 or later | Default. Modern curve, short keys, the one I reach for. |
ecdsa-sk | NIST P-256 | 5.1.x and later | Only when the key's firmware is too old for ed25519-sk. |
Both require OpenSSH 8.2+ on the client that generates and uses the key, and on the server you log in to (the server has to understand the sk key type in authorized_keys).
One trap that catches people on minimal Linux installs: OpenSSH talks to the YubiKey through a middleware library, libfido2, and the ssh-sk-helper binary that ships with OpenSSH. If ssh-keygen -t ed25519-sk fails with something like Couldn't get key types: error in libcrypto or a flat "provider" error even though ssh -V shows 8.2+, you are usually missing libfido2 (sudo apt install libfido2-1 on Debian/Ubuntu, libfido2 on Fedora/Arch). Distro-packaged OpenSSH normally pulls it in; hand-built or stripped-down images often do not.
Resident keys: surviving a fresh laptop
By default the handle file matters: lose id_ed25519_sk and you cannot use the key even with the YubiKey, because the handle is half of what is needed. A resident (discoverable) key changes that. It stores the handle on the YubiKey itself, so you can walk up to any machine, plug the key in, and pull the credential back down.
ssh-keygen -t ed25519-sk -O residentOn a fresh machine, retrieve the resident keys straight off the hardware:
ssh-keygen -KThat writes id_ed25519_sk_rk and its .pub into the current directory. The trade-off is real: resident keys consume one of the YubiKey's limited FIDO2 credential slots, and anyone who has both the unlocked YubiKey and (if set) its FIDO2 PIN can extract them. They also need firmware 5.2.3 or later; older 5.1.x keys do FIDO2 but not discoverable credentials. For most people the non-resident default is the right call. Reach for -O resident only when "I need to log in from a machine that has never seen my key handle" is a genuine requirement, for example a recovery scenario or a shared jump box.
Useful generation options
ssh-keygen exposes the FIDO2 options through -O:
| Option | Effect |
|---|---|
-O resident | Store the handle on the key itself (discoverable). |
-O verify-required | Require a PIN (or biometric) in addition to the touch. |
-O no-touch-required | Drop the touch requirement (weakens the guarantee; most servers reject it). |
-O application=ssh:work | Tag the credential with a custom application string to keep multiple keys apart. |
-O verify-required is the one I add for anything sensitive. It means a stolen-and-plugged-in YubiKey is not enough; the attacker also needs the FIDO2 PIN. The server can enforce it too, with verify-required in authorized_keys, so the requirement is checked on both ends. Note that this option needs OpenSSH 8.4 or newer (the per-use PIN requirement landed two releases after the base sk key types), and a YubiKey on firmware 5.2.3 or later; firmware 5.1.x does not support it. You set the PIN itself with ykman fido access change-pin; without one set, verify-required cannot enroll.
Install the public key and log in
Identical to any other SSH key. Append id_ed25519_sk.pub to the server's authorized_keys:
ssh-copy-id -i ~/.ssh/id_ed25519_sk.pub user@serverThen connect. The difference shows up at login: the YubiKey blinks and SSH waits for your tap before it will authenticate.
ssh user@server
# Confirm user presence for key ED25519-SK ...
# (LED blinks, tap the YubiKey)A few things to keep straight once it works:
- The handle file (
id_ed25519_sk) still needs600permissions like any private key, even though it is not secret on its own. SSH checks the mode regardless, and a too-open handle triggers the same UNPROTECTED PRIVATE KEY FILE error you get with a software key. - This pairs well with disabling password authentication on the server: once a hardware key is your login, password auth is pure attack surface.
- The passphrase prompt during generation encrypts the handle file, not the YubiKey secret. You can change or remove that passphrase later with
ssh-keygen -p, exactly as with a software key.
When not to bother
Hardware keys are not free in friction, and there are spots where a plain Ed25519 software key is the better tool:
- Unattended automation. CI runners, cron jobs, and deploy bots cannot tap a YubiKey. A FIDO2 key requiring user presence is the wrong choice for anything that must authenticate without a human present. Use a scoped software key (or short-lived certificates) there.
- Servers older than OpenSSH 8.2. The server side has to understand
skkey types. A box stuck on an ancient OpenSSH will reject the public key outright. Checkssh -Von the server before committing. - You only have one key. A single YubiKey is a single point of failure: lose it and you are locked out of everything that trusts it. Enroll a backup key on every account from day one, the same way you would with any FIDO2 setup.
For everyday interactive logins from a laptop you carry around, though, a FIDO2 SSH key is the strongest practical setup: the private key is in silicon you control, and every login needs a deliberate physical tap.
It stands for "security key". ssh-keygen -t ed25519-sk generates an Ed25519 key whose private half is created and stored inside a FIDO2 authenticator like a YubiKey. What gets written to ~/.ssh is a key handle, not the private scalar, so the secret never touches your disk.
The same suffix exists for ECDSA as ecdsa-sk, for keys whose firmware is too old for Ed25519.
OpenSSH 8.2 or newer, on both the client that generates and uses the key and the server you log in to. FIDO/U2F support landed in the 8.2 release in February 2020. You can check with ssh -V. The server has to understand the sk key type in authorized_keys, so an older box will reject the public key even if your laptop is current.
ed25519-sk requires YubiKey firmware 5.2.3 or later. Earlier hardware only supports ecdsa-sk. YubiKey firmware cannot be upgraded after manufacture, so an older key is permanently limited to ecdsa-sk. Run ykman info to read the firmware version. The ECDSA variant is still hardware-backed and perfectly usable; it just uses the NIST P-256 curve instead of Ed25519.
A resident (discoverable) key stores the key handle on the YubiKey itself, generated with ssh-keygen -t ed25519-sk -O resident. You can then plug the key into any fresh machine and pull the credential back with ssh-keygen -K, with no handle file needed beforehand. Most people do not need this: the non-resident default is fine and does not consume a credential slot. Use resident keys only when logging in from machines that have never seen your handle file is a real requirement.
You copy the handle file and public key, but not the private key, because the private key never leaves the YubiKey. On the new machine the handle is only usable with that physical YubiKey plugged in. If you generated a resident key, you do not even need to copy files: run ssh-keygen -K to retrieve the credential directly from the hardware.
Yes. ssh-add ~/.ssh/id_ed25519_sk loads the handle into the agent like any key, so you are not retyping the handle passphrase all day. The touch requirement is not cached, though: the agent still asks for a physical tap on every authentication, because user presence is asserted by the hardware per signature, not held in the agent. That is the whole security property, so it is working as intended, not a bug. For a resident key you do not have the handle file on a fresh box, so run ssh-keygen -K first, then ssh-add the retrieved file.
You cannot back up the private key itself; that is the point, it never leaves the hardware, so there is nothing to copy. The backup strategy is a second physical key. Generate a separate FIDO2 key on a spare YubiKey and add both public keys to every server's authorized_keys from day one. If you lose the primary, the backup still authenticates, and you revoke the lost key by removing its public line. This is the same enroll-a-backup discipline FIDO2 uses everywhere; treat the SSH key no differently. Pairing it with disabled password auth means a lost key never falls back to a weaker login.
See also
- How to create an SSH key: the software-key baseline, picking Ed25519 over RSA, passphrases, and the file permissions SSH insists on.
- Harden sshd by disabling password authentication: the natural next step once a hardware key is your login, so passwords stop being an attack surface.
- Change an SSH key passphrase: how
ssh-keygen -pworks, which also covers the passphrase on the FIDO2 handle file. - Fix "Permissions are too open": what to do when SSH rejects the handle file because its mode is wider than
600. - SSH cheat sheet: the quick reference for
ssh-keygen, copying keys to servers, and the rest of the day-to-day SSH commands.
Sources
Authoritative references this article was fact-checked against.
- ssh-keygen(1) manual page (OpenBSD)man.openbsd.org
- OpenSSH 8.2 release notes (FIDO/U2F support)openssh.com
- OpenSSH 8.4 release notes (verify-required, per-use FIDO PIN)openssh.com
- Securing SSH with FIDO2 (Yubico Developers)developers.yubico.com





