YubiKey and FIDO2 with SSH
OpenSSH has supported FIDO2 security keys natively since version 8.2 (February 2020). A YubiKey can hold the SSH credential itself, so the private key never lives on disk and every login needs the physical key. This post covers setting up a YubiKey for SSH, the trade-offs between key types, and what has changed in recent OpenSSH and YubiKey firmware releases.
Updated August 2026 for OpenSSH 10.5 and YubiKey firmware 5.7. Version requirements,
ykmansyntax and server-side options have been corrected, and a troubleshooting section added.
Requirements
| Component | Requirement | Notes |
|---|---|---|
| OpenSSH client and server | 8.2 or newer | Non-resident keys (ecdsa-sk, ed25519-sk) |
| OpenSSH client | 8.3 or newer | Resident keys (-O resident, ssh-add -K, ssh-keygen -K) |
| OpenSSH client and server | 8.4 or newer | -O verify-required (PIN enforcement) |
| YubiKey firmware | 5.2.3 or newer | Required for ed25519-sk and for resident keys. Older keys must use ecdsa-sk and non-resident keys |
ykman (YubiKey Manager CLI) | 5.x | For PIN and credential management |
Check your versions before starting:
1
2
ssh -V
ykman info
Every current Linux distribution ships an OpenSSH that meets these requirements (Debian 12 has 9.2, Ubuntu 22.04 has 8.9, Ubuntu 24.04 has 9.6). On macOS the bundled OpenSSH is built without FIDO support, so install it with Homebrew (brew install openssh). On Windows the built-in client supports FIDO from 8.9 onwards; run ssh -V in PowerShell to confirm, or install the latest Win32-OpenSSH release.
Initial Setup
To use FIDO2 on a YubiKey you need to set a FIDO2 PIN first. The ykman command line tool does this. The old YubiKey Manager GUI has been replaced by Yubico Authenticator, but the CLI is still maintained and is what is used throughout this post.
1
2
3
4
5
6
7
8
# Install the YubiKey Manager CLI
sudo apt install yubikey-manager -y
# Check the FIDO2 application status, including whether a PIN is set
ykman fido info
# Set the PIN (also used to change it later)
ykman fido access change-pin
Changing the PIN does not affect your credentials. Resetting the FIDO2 application (
ykman fido reset) wipes every FIDO2 and U2F credential on the key and removes the PIN. You would need to re-register the key with every service.
The FIDO2 application locks after 8 consecutive wrong PIN entries, after which the only way forward is a full reset. After 3 consecutive wrong entries the YubiKey must be unplugged and reinserted before it will accept another attempt, which is a useful safeguard against typing it wrong repeatedly.
PIN rules depend on firmware:
- Firmware before 5.7: minimum 4 characters
- Firmware 5.7 and newer: minimum 4 characters, or 6 when PIN complexity is enabled (which also rejects repeated and sequential characters)
- FIPS series: minimum 6 characters, 8 on firmware 5.7.4 and newer
The PIN can be alphanumeric, so treat it as a short password rather than a numeric code.
Key Types
With SSH and FIDO2 there are two kinds of key you can generate.
- Non-resident key (non-discoverable). The YubiKey never stores the key.
ssh-keygenwrites a small key handle to~/.sshand the YubiKey re-derives the private key from that handle whenever it is used. Both the handle file and the YubiKey are needed to authenticate. Requires OpenSSH 8.2 or newer. Works on any FIDO2 YubiKey and does not use up credential slots. - Resident key (discoverable). The credential is stored on the YubiKey itself, so it can be recovered onto any machine with
ssh-keygen -Kor loaded straight into an agent withssh-add -K. Anyone with the YubiKey and the PIN can retrieve it. Requires OpenSSH 8.3 or newer and firmware 5.2.3 or newer.
Non-resident keys are the more secure default because losing the YubiKey alone does not expose the credential. Resident keys are more convenient if you move between machines. Whichever you choose, Yubico recommends adding -O verify-required so the PIN is always needed.
A YubiKey with firmware 5.7 or newer stores up to 100 resident credentials. Older 5 series keys store 25. Non-resident keys are unlimited.
Choosing the algorithm
Use ed25519-sk unless your YubiKey is older than firmware 5.2.3, in which case use ecdsa-sk. All the examples below use ed25519-sk; swap the type if needed.
Touch and PIN options
The -O flags control which factors are required each time the key is used. They apply to both resident and non-resident keys.
| Factors | ssh-keygen options | Description |
|---|---|---|
| Touch only (default) | (none) | Touch the YubiKey to authenticate. No PIN |
| PIN and touch (recommended) | -O verify-required | Both the PIN and a touch are required |
| PIN only | -O verify-required -O no-touch-required | PIN required, no touch. Needs a server-side option, see below |
| Neither | -O no-touch-required | Nothing beyond possession of the key. Needs a server-side option, see below |
no-touch-requiredis refused bysshdunless the matchingauthorized_keysentry is prefixed with theno-touch-requiredoption. Without it you will still be asked to touch the key. This is deliberate: the server, not the client, decides whether user presence can be skipped.
Generating a Non-Resident Key
1
2
3
ssh-keygen -t ed25519-sk -O verify-required \
-C "laptop yubikey 10532500" \
-f ~/.ssh/id_ed25519_sk
Touch the key when it flashes. You get ~/.ssh/id_ed25519_sk (the key handle, useless without the YubiKey) and ~/.ssh/id_ed25519_sk.pub. Use them exactly like a normal key pair. Keep the handle file backed up with your dotfiles; if it is lost the key cannot be recovered.
Generating a Resident Key
The application option tags the credential so you can store more than one SSH key on the same YubiKey. The value must start with ssh:. Without it every SSH credential uses the default ssh: application and generating a second one overwrites the first.
1
2
3
4
5
6
7
8
9
10
11
12
# Template
ssh-keygen -t ed25519-sk -O resident -O verify-required \
-O application=ssh:your-text-here \
-C "comment or identifier"
# Example
ssh-keygen -t ed25519-sk -O resident -O verify-required \
-O application=ssh:servers \
-C "Emergency Key 1 - 10532500"
# Verify the credential was stored on the YubiKey
ykman fido credentials list
ykman fido credentials list asks for the PIN and prints one line per credential showing the application (RP ID), the user name (openssh) and the credential ID. Add --csv for the full details.
Using a resident key on another machine
Either write the key handles out as files:
1
2
cd ~/.ssh
ssh-keygen -K
This creates id_ed25519_sk_rk_<application> and .pub files for every resident SSH credential on the YubiKey. Since OpenSSH 10.3 the comment is set to the application string, so the files are easy to tell apart.
Or load them directly into a running agent without writing anything to disk:
1
2
3
eval "$(ssh-agent -s)"
ssh-add -K
ssh-add -L
Both commands ask for the PIN because enumerating resident credentials always requires it.
PIN Prompts and the Agent
When you use a key file directly with ssh, the PIN prompt appears in the terminal and nothing extra is needed. When the key lives in ssh-agent, the agent has no terminal, so it needs an askpass helper to collect the PIN.
1
2
3
4
5
6
7
8
9
# Install an askpass helper (any of these work)
sudo apt install ssh-askpass -y # X11
sudo apt install ssh-askpass-gnome -y
# Point the agent at it. Only needed if it is not picked up automatically
export SSH_ASKPASS=/usr/bin/ssh-askpass
# OpenSSH 8.4+: force the prompt into the terminal instead of a GUI window
export SSH_ASKPASS_REQUIRE=force
A helper for your ~/.bashrc:
1
2
export SSH_ASKPASS=/usr/bin/ssh-askpass
alias yubikey_ssh='eval "$(ssh-agent -s)" && ssh-add -K && ssh-add -L'
When connecting you will be prompted for the PIN, then the command line will appear to hang. Touch the YubiKey to complete the login.
If you use
ssh -A(agent forwarding) the remote host can ask your YubiKey to sign, but you still have to touch it locally for each use. Avoid forwarding unless you need it.ssh -J(ProxyJump) does not require forwarding.
Server-Side Enforcement
The verify-required and no-touch-required flags baked into a key are honoured by the client, but the server can also insist on them. Per key in authorized_keys:
1
2
verify-required sk-ssh-ed25519@openssh.com AAAA... Emergency Key 1
no-touch-required sk-ssh-ed25519@openssh.com AAAA... build-bot
Or globally in sshd_config (OpenSSH 8.4+):
1
PubkeyAuthOptions verify-required touch-required
With PubkeyAuthOptions set, a user cannot bypass the policy by generating a key without -O verify-required. touch-required there simply rejects keys created with no-touch-required.
Managing Credentials
1
2
3
4
5
6
7
8
9
10
# List resident credentials on the YubiKey
ykman fido credentials list
# Delete one. The argument is any unique substring of the credential ID
# shown in the list output, so use the ID rather than the "ssh:" name
# when you have more than one SSH credential on the key
ykman fido credentials delete a1b2c3d4
# Wipe the whole FIDO2 application (all credentials, all services, PIN)
ykman fido reset
Since OpenSSH 10.5,
ssh-keygen -p -O verify-required -f ~/.ssh/id_ed25519_skcan add or remove theverify-requiredandtouch-requiredflags on an existing FIDO key file when resetting its passphrase, so you no longer need to regenerate a key to tighten it.
Best Practices
- Have a backup YubiKey. A FIDO key cannot be copied. Generate a separate key on a second YubiKey and add both public keys to your servers. Note down which is which in the
-Ccomment. - Use
-O verify-required. Touch alone proves the key is present; the PIN proves it is you. This is also Yubico’s recommendation. - Prefer non-resident keys unless you actually move between machines. Losing the YubiKey then reveals nothing.
- Use
-O application=ssh:...for every resident key so you never overwrite one by accident. - Enforce on the server with
PubkeyAuthOptions verify-requiredso client mistakes do not weaken the policy. - Do not put the FIDO PIN on the same lockout budget as everything else. Eight wrong attempts wipes the FIDO2 application, which also covers passkeys registered with other services on that key.
- GitHub, GitLab and others accept
sk-ssh-ed25519@openssh.compublic keys. Paste the.pubfile as usual and test withssh -T git@github.com.
Troubleshooting
| Symptom | Cause / Fix |
|---|---|
Key enrollment failed: requested feature not supported | Firmware older than 5.2.3. Use ecdsa-sk, or drop -O resident |
Key enrollment failed: invalid format on ed25519-sk | Same as above, the key cannot do Ed25519 in FIDO2 |
unknown key type ed25519-sk | OpenSSH built without FIDO support (macOS bundled ssh). Install via Homebrew |
Confirm user presence hangs | Touch the key. If it never flashes, check libfido2 can see it: fido2-token -L |
sign_and_send_pubkey: signing failed ... agent refused operation | The agent could not get the PIN. Set SSH_ASKPASS or SSH_ASKPASS_REQUIRE=force |
Permission denied with no-touch-required key | Add the no-touch-required option to the authorized_keys line on the server |
ykman cannot open the device on Linux | Missing udev rules. Install libfido2-1 / libu2f-udev and replug the key |