Tools: YubiKey SSH Authentication: Stop Trusting Key Files on Disk

Tools: YubiKey SSH Authentication: Stop Trusting Key Files on Disk

Why a Hardware Key Beats a Private Key File

Which YubiKey to Get

Setting Up SSH with FIDO2 Resident Keys

Adding the Key to Remote Servers

The Resident Key Trick: Any Machine, No Key Files

Hardening sshd for Key-Only Auth

What About Git and GitHub?

Gotchas I Hit

My Setup

Should You Bother? I stopped using SSH passwords three years ago. Switched to ed25519 keys, felt pretty good about it. Then my laptop got stolen from a coffee shop — lid open, session unlocked. My private key was sitting right there in ~/.ssh/, passphrase cached in the agent. That’s when I bought my first YubiKey. Your SSH private key lives on disk. Even if it’s passphrase-protected, once the agent unlocks it, it’s in memory. Malware can dump it. A stolen laptop might still have an active agent session. Your key file can be copied without you knowing. A YubiKey stores the private key on the hardware. It never leaves the device. Every authentication requires a physical touch. No touch, no auth. Someone steals your laptop? They still need the physical key plugged in and your finger on it. That’s the difference between “my key is encrypted” and “my key literally cannot be extracted.” For SSH, you want a YubiKey that supports FIDO2/resident keys. Here’s what I’d recommend: YubiKey 5C NFC — my top pick. USB-C fits modern laptops, and the NFC means you can tap it on your phone for GitHub/Google auth too. Around $55, and I genuinely think it’s the best value if you work across multiple devices. If you’re on a tighter budget, the YubiKey 5 NFC (USB-A) does the same thing for about $50, just with the older port. Still a good option if your machines have USB-A. One important note: buy two. Register both with every service. Keep one on your keychain, one locked in a drawer. If you lose your primary, you’re not locked out of everything. I learned this the hard way with a 2FA lockout that took three days to resolve. You need OpenSSH 8.2+ (check with ssh -V). Most modern distros ship with this. If you’re on macOS, the built-in OpenSSH works fine since Ventura. First, generate a resident key stored directly on the YubiKey: It’ll ask you to set a PIN if you haven’t already. Pick something decent — this is your second factor alongside the physical touch. You’ll end up with two files: id_ed25519_sk and id_ed25519_sk.pub. The private file is actually just a handle — the real private key material lives on the YubiKey. Even if someone gets this file, it’s useless without the physical hardware. Or manually append the public key to ~/.ssh/authorized_keys on the target machine. When you SSH in, you’ll see: That “confirm user presence” line means it’s waiting for you to physically tap the YubiKey. No tap within ~15 seconds? Connection refused. I love this — it’s impossible to accidentally leave a session auto-connecting in the background. This is the feature that sold me. Because the key is resident (stored on the YubiKey itself), you can pull it onto any machine: That’s it. Plug in your YubiKey, run that command, and it downloads the key handles to your current machine. Now you can SSH from a fresh laptop, a coworker’s machine, or a server — as long as you have the YubiKey plugged in. No more syncing ~/.ssh folders across machines. No more “I need to get my key from my other laptop.” The YubiKey is the key. Once your YubiKey is working, lock down the server. In /etc/ssh/sshd_config: Reload sshd (systemctl reload sshd) and test with a new terminal before closing your current session. I’ve locked myself out exactly once by reloading before testing. Don’t be me. If you want to go further, you can restrict to only FIDO2 keys by requiring the sk key types in your authorized_keys entries. But for most setups, just disabling passwords is the big win. GitHub has supported security keys for SSH since late 2021. Add your id_ed25519_sk.pub in Settings → SSH Keys, same as any other key. Every git push and git pull now requires a physical touch. It adds maybe half a second to each operation. I was worried this would be annoying — it’s actually reassuring. Every push is a conscious decision. For your Git config, make sure you’re using the SSH URL format: Agent forwarding doesn’t work with FIDO2 keys. The touch requirement is local — you can’t forward it through an SSH jump host. If you rely on agent forwarding, you’ll need to either set up ProxyJump or keep a regular ed25519 key for jump scenarios. macOS Sonoma has a quirk where the built-in SSH agent sometimes doesn’t prompt for the touch correctly. Fix: add SecurityKeyProvider internal to your ~/.ssh/config. WSL2 can’t see USB devices by default. You’ll need usbipd-win to pass the YubiKey through. It works fine once set up, but the initial config is a 10-minute detour. VMs need USB passthrough configured. In VirtualBox, add a USB filter for “Yubico YubiKey.” In QEMU/libvirt, use hostdev passthrough. This catches people off guard when they SSH from inside a VM and wonder why the key isn’t detected. I carry a YubiKey 5C NFC on my keychain and keep a backup YubiKey 5 Nano in my desk. The Nano stays semi-permanently in my desktop’s USB port — it’s tiny enough that it doesn’t stick out. Both keys are registered on every server, GitHub, and every service that supports FIDO2. If I lose my keychain, I walk to my desk and keep working. Total cost: about $80 for two keys. For context, that’s less than a month of most password manager premium plans, and it protects against a class of attacks that passwords simply can’t. If you SSH into anything regularly — servers, homelabs, CI runners — yes. The setup takes 15 minutes, and the daily friction is a light tap on a USB device. The protection you get (key material that physically can’t be stolen remotely) is worth way more than the cost. If you’re already running a homelab with TrueNAS or managing Docker containers, this is a natural next step in locking things down. Hardware keys fill the gap between “I use SSH keys” and “my infrastructure is actually secure.” Start with one key, test it for a week, then buy the backup. You won’t go back. Templates let you quickly answer FAQs or store snippets for re-use. Hide child comments as well For further actions, you may consider blocking this person and/or reporting abuse

Command

Copy

$ ssh-keygen -t ed25519-sk -O resident -O verify-required -C "yubikey-primary" ssh-keygen -t ed25519-sk -O resident -O verify-required -C "yubikey-primary" ssh-keygen -t ed25519-sk -O resident -O verify-required -C "yubikey-primary" ssh-copy-id -i ~/.ssh/id_ed25519_sk.pub user@your-server ssh-copy-id -i ~/.ssh/id_ed25519_sk.pub user@your-server ssh-copy-id -i ~/.ssh/id_ed25519_sk.pub user@your-server Confirm user presence for key ED25519-SK SHA256:... User presence confirmed Confirm user presence for key ED25519-SK SHA256:... User presence confirmed Confirm user presence for key ED25519-SK SHA256:... User presence confirmed ssh-keygen -K ssh-keygen -K ssh-keygen -K PasswordAuthentication no KbdInteractiveAuthentication no PubkeyAuthentication yes AuthenticationMethods publickey PasswordAuthentication no KbdInteractiveAuthentication no PubkeyAuthentication yes AuthenticationMethods publickey PasswordAuthentication no KbdInteractiveAuthentication no PubkeyAuthentication yes AuthenticationMethods publickey -weight: 500;">git remote set-url origin -weight: 500;">git@github.com:username/repo.-weight: 500;">git -weight: 500;">git remote set-url origin -weight: 500;">git@github.com:username/repo.-weight: 500;">git -weight: 500;">git remote set-url origin -weight: 500;">git@github.com:username/repo.-weight: 500;">git - -t ed25519-sk — uses the ed25519 algorithm backed by a security key (sk = security key) - -O resident — stores the key on the YubiKey, not just a reference to it - -O verify-required — requires PIN + touch every time (not just touch) - -C "yubikey-primary" — label it so you know which key this is