Two-Factor Authentication for SSH: TOTP Codes and FIDO2 Security Keys

Two-Factor Authentication for SSH: TOTP Codes and FIDO2 Security Keys

SSH keys already beat passwords by a long way. But a private key is a file, and files get copied: by malware, from a stolen laptop, from a backup someone forgot to encrypt. Two-factor authentication makes a stolen key insufficient on its own.

There are two practical ways to do it on Linux:

  1. TOTP codes: the six-digit codes from an authenticator app, checked by the server through PAM
  2. FIDO2 security keys: SSH keys whose private half lives in a YubiKey or similar, requiring a physical touch

If SSH keys are new to you, start with SSH keys explained and the SSH hardening guide.

Before you change anything

Changing SSH authentication is the classic way to lock yourself out of a remote server. Every time:

  • Keep an existing SSH session open while you test in a second terminal
  • Validate the configuration before reloading:
sudo sshd -t
  • Know how to reach the console (your VPS provider’s web console, Proxmox, IPMI) if it goes wrong

Method 1: TOTP with pam_google_authenticator

Despite the name, this module has nothing to do with Google services: it implements the standard TOTP algorithm, and works with any authenticator app, including Aegis, 2FAS, Ente Auth, or a password manager.

Install and enroll

sudo apt install libpam-google-authenticator   # Debian / Ubuntu
sudo dnf install google-authenticator          # Fedora (EPEL on RHEL)
sudo pacman -S libpam-google-authenticator     # Arch

Then, as the user who will log in (not root):

google-authenticator

Answer yes to time-based tokens. It shows a QR code to scan with your app, and prints emergency scratch codes: save those somewhere safe, they are your way in if you lose the phone. Answer yes to updating the ~/.google_authenticator file, and to disallowing reuse of codes.

Configure PAM

Edit /etc/pam.d/sshd and add near the top:

auth required pam_google_authenticator.so

If you want to roll it out gradually, nullok lets users who have not enrolled yet log in without a code:

auth required pam_google_authenticator.so nullok

To require only key + code and not also prompt for a password, comment out the line that includes common password authentication on Debian and Ubuntu:

# @include common-auth

Our PAM explainer covers how these stacks work, and why order matters.

Configure sshd

Create /etc/ssh/sshd_config.d/50-2fa.conf:

UsePAM yes
KbdInteractiveAuthentication yes
AuthenticationMethods publickey,keyboard-interactive

The comma in AuthenticationMethods means both are required: a valid key first, then the verification code. (ChallengeResponseAuthentication in older guides is a deprecated alias for KbdInteractiveAuthentication.)

Test and reload, keeping your existing session open:

sudo sshd -t && sudo systemctl reload ssh    # 'sshd' on Fedora/Arch

From a new terminal:

$ ssh alice@server
(alice@server) Verification code:

Method 2: FIDO2 hardware keys (ed25519-sk)

Since OpenSSH 8.2, SSH supports keys backed by FIDO2 security keys. The file in ~/.ssh is only a handle: the private key never leaves the hardware, and signing requires the key to be present and touched. Malware that copies your .ssh directory gets nothing usable.

Generate a key

Plug in the security key, then:

ssh-keygen -t ed25519-sk -C "laptop yubikey"

Touch the key when it blinks. Useful options:

OptionEffect
-O residentStore the key handle on the security key, so you can use it from any machine with ssh-keygen -K
-O verify-requiredAlso require the key’s PIN, not just a touch
-O application=ssh:workSeparate resident keys by name

-O verify-required turns it into true two-factor: something you have (the key) and something you know (its PIN).

If ed25519-sk fails, the security key may not support Ed25519; -t ecdsa-sk is the fallback. On Linux the key needs udev permissions, normally provided by the libfido2 package; see passkeys on Linux for setup.

Install the public key on the server

ssh-copy-id -i ~/.ssh/id_ed25519_sk.pub alice@server

The server needs OpenSSH 8.2 or newer, which every current distribution has. Nothing else changes server-side; it is a public key like any other.

Require it

To make hardware-backed keys the only accepted kind, list only sk- key types:

PubkeyAcceptedAlgorithms sk-ssh-ed25519@openssh.com,sk-ecdsa-sha2-nistp256@openssh.com

That rejects ordinary key files entirely. Make sure every account has a hardware key enrolled first.

Avoid locking yourself out

  • Register two security keys per account (add both public keys to authorized_keys), and keep one somewhere safe
  • Save TOTP scratch codes offline
  • Consider leaving 2FA off for one break-glass admin account that only accepts logins from your VPN, using a Match Address block
  • Keep provider console access working, and test it occasionally

TOTP or FIDO2?

TOTPFIDO2 key
Hardware neededAny phoneA security key (about the price of a nice lunch)
Phishing-resistantNoYes
Stops stolen key filesYes (code also needed)Yes (file is useless without hardware)
Daily frictionType a codeTouch the key
Client supportAny SSH clientOpenSSH 8.2+ clients

For your own machines, FIDO2 is both stronger and more pleasant. TOTP is easier to roll out to many users with mixed clients. They also combine with Fail2ban or CrowdSec for rate limiting, and with an SSH certificate authority for larger fleets.

Frequently Asked Questions

Is SSH key authentication not already two-factor?

Not quite. A key file is one factor, something you have, and a passphrase on it is a second factor only on your own machine, since the server cannot verify the passphrase was used. Server-enforced 2FA with TOTP, or a FIDO2 key that requires a physical touch, adds a factor the server can actually check.

What is an ed25519-sk key?

An SSH key whose private part lives on a FIDO2 hardware security key such as a YubiKey. The file on disk is only a handle; signing requires the physical key to be plugged in and touched. Supported since OpenSSH 8.2 on both client and server.

Which setting enables TOTP prompts in sshd?

KbdInteractiveAuthentication yes, together with UsePAM yes and the pam_google_authenticator module in /etc/pam.d/sshd. Older guides call the setting ChallengeResponseAuthentication, which is a deprecated alias for the same thing.

How do I require both a key and a code?

Set AuthenticationMethods publickey,keyboard-interactive in sshd_config. The comma means both are required in that order: the client must present a valid key, then answer the PAM prompt for the verification code.

What if I lose my phone or security key?

For TOTP, use one of the emergency scratch codes printed when you ran google-authenticator, which is why you should save them. For FIDO2 keys, register a second hardware key in authorized_keys, or keep console access available so you can log in and fix authorized_keys.

Should I use TOTP or a FIDO2 key?

A FIDO2 key is stronger and more convenient day to day, since it resists phishing and malware stealing key files and needs only a touch. TOTP works with any phone and any SSH client, which makes it easier to roll out to many users or older systems.