Published 2026-10-09 · Updated 2026-10-22
SSH Two-Factor Authentication: Hardware Keys and TOTP Without Locking Yourself Out
SSH key authentication is the single best step for a VPS, but a key is only one factor: something you have, stored as a file. If that file is copied from a laptop, a backup or a compromised workstation, the attacker has everything they need. Two-factor authentication closes that gap by requiring something more at login. The definition is simple; the risk is in the rollout, because a mistake in sshd_config can lock you out of your own server. This guide shows two approaches, a FIDO2 hardware-backed key and a TOTP code through PAM, and a rollout order that keeps a way back in at every step. It builds on the SSH hardening guide, so do that first.
In this article
Why a key alone is not enough Choose a second factor Option A: a hardware-backed FIDO2 key Option B: TOTP with PAM Roll out without locking yourself out Limits and what to pair it with Frequently asked questionsQuick win (10 minutes)
- Check your OpenSSH version on both ends with
ssh -V. FIDO keys need a client and a server that support them. - Open a second SSH session to the server and leave it connected for the rest of this work.
- Confirm your VPS provider offers a web console or rescue mode you can use if SSH breaks.
- Confirm you can run
sudo sshd -tto validate configuration before reloading. - Decide who needs a second factor first: start with accounts that can use sudo.
Why a key alone is not enough
A private key file can be copied silently. Malware on a developer machine, an unencrypted backup or a leaked disk image all give an attacker a working login with no failed attempts for you to see. A passphrase on the key helps, but it protects the file, not the login. A second factor means a stolen file is not enough on its own. CISA states that "users who enable MFA are significantly less likely to get hacked," and, more usefully for choosing a method, that "any MFA is better than no MFA," while phishing-resistant MFA is "the standard all industry leaders should strive for." It also notes that "the only widely available phishing-resistant authentication is FIDO/WebAuthn."
That framing guides the choice for SSH. A FIDO2 security key is the stronger option, because the private key never leaves the device and every login needs a physical touch. A TOTP code is weaker, since a code can be entered into a fake prompt, but it is far better than no second factor, works on any server and costs nothing. If you have already read about how attackers get into small servers, you know that stolen credentials remain a main way in.
Choose a second factor
Compare the two on what matters for a small server:
- FIDO2 key (
ed25519-sk): hardware-backed, touch required, no codes to type, no extra server software. Needs OpenSSH 8.2 or later and a physical security key for each administrator. Best choice if you can buy two keys, one spare. - TOTP via PAM: works with any authenticator app, no hardware, runs on older systems. Adds a PAM module on the server and a per-user setup step. A code can be phished or relayed in real time.
You can also combine them, but for one or two administrators pick one and do it properly. Whichever you choose, keep key-based login as the first factor and password login off.
Option A: a hardware-backed FIDO2 key
OpenSSH 8.2 added support for FIDO/U2F hardware authenticators. According to the release notes, FIDO devices are supported through new public key types, ecdsa-sk and ed25519-sk, using a built-in middleware that depends on libfido2. Generate a key on your own machine with the security key plugged in:
ssh-keygen -t ed25519-sk -O verify-required -C "admin-yubikey-1"
The ssh-keygen manual explains the options. FIDO authenticators generally require the user to authorise operations by touching them. verify-required indicates that the private key "should require user verification for each signature," and PIN is currently the only supported method. If you add -O resident, the key handle is stored on the authenticator itself, which typically requires a PIN to be set first. Avoid no-touch-required, which removes the presence check; sshd refuses such signatures by default.
Copy the public key (id_ed25519_sk.pub) into ~/.ssh/authorized_keys on the server. To make the server insist on user verification for FIDO keys, set this in sshd_config:
PubkeyAuthOptions verify-required
The sshd_config manual says verify-required "requires a FIDO key signature attest that the user was verified, e.g. via a PIN," and that it has no effect on non-FIDO key types. That last point matters: this setting alone does not stop someone logging in with an ordinary key still listed in authorized_keys. Once your FIDO key works, remove the plain keys for administrators. Register a second hardware key as a spare and store it somewhere safe.
Want your SSH setup reviewed?
A free, read-only server check looks at how SSH is configured and exposed, without touching your settings.
Get your free server check Talk to a security engineerOption B: TOTP with PAM
The Google Authenticator PAM module produces time-based codes that any standard authenticator app can generate. Install it (on Debian and Ubuntu the package is libpam-google-authenticator), then each user runs google-authenticator once to create a secret in their home directory and scan the QR code. Save the emergency scratch codes it prints somewhere offline.
The project's documentation shows the PAM line as auth required pam_google_authenticator.so. Its nullok option lets users who have not set up a code yet log in without one, which is useful during rollout but must be removed afterwards. The module tries to compensate for time skew, and if a code never works the README advises waiting 30 seconds (but not longer) so that you get three distinct codes. Keep the server clock synchronised.
Then tell sshd to require a key and then the code. The sshd_config manual defines AuthenticationMethods as the methods that "must be successfully completed for a user to be granted access" and notes that each method listed "should also be explicitly enabled." So enable keyboard-interactive and require both:
KbdInteractiveAuthentication yes
UsePAM yes
PasswordAuthentication no
AuthenticationMethods publickey,keyboard-interactive
With the comma, the key is checked first and the code second. On Debian and Ubuntu, /etc/pam.d/sshd normally also includes the system password stack, so comment out the @include common-auth line there if you want the verification code only, rather than a password prompt as well. Test every change as described next.
Roll out without locking yourself out
Changing authentication is the one place where a typo costs you server access. Follow this order every time:
- Keep your existing SSH session open and a second one connected.
- Back up the config:
sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak. Check files in/etc/ssh/sshd_config.d/too, because they can override your setting. - Validate syntax with
sudo sshd -t, and only then reload withsudo systemctl reload ssh(the unit issshdon some distributions). - From a third terminal, open a brand-new connection and confirm it works. Do not close your old sessions until it does.
- Roll out one administrator at a time. Use
nulloktemporarily for TOTP, then remove it once everyone is enrolled. - Keep an escape route: your provider's web console or rescue mode, and the scratch codes or a spare hardware key.
Document how a new administrator enrols and how you revoke someone who leaves. The authorized_keys file is the list of who can get in, so review it regularly; see the authorized_keys entry.
Limits and what to pair it with
A second factor protects the login, not the server. It does not help if a service is vulnerable, if an attacker already holds a session, or if the admin account can do anything once inside. Pair it with the rest of the defence: keep fail2ban and rate limits on SSH, restrict who can log in with AllowUsers, and limit what each account can do afterwards, as covered in users, sudo and least privilege.
Also remember that automation breaks if it must supply a second factor. Deploy jobs and backup pulls should use separate, restricted keys with forced commands, and never share an administrator's hardware key. If something does go wrong and you suspect a stolen key or session, follow the breach response guide, and keep this change on your security checklist.
If a change to authentication leaves you unable to log in, use locked out of your VPS after changing SSH. Background: TOTP, FIDO2 and WebAuthn and PAM.
Frequently asked questions
Do I need two-factor authentication for SSH if I already use keys?
It is strongly worth adding. A key is a file that can be copied without any sign of it, while a second factor means a stolen file alone does not give access. Keys without a second factor are still far better than passwords.
What is the difference between FIDO2 and TOTP for SSH?
A FIDO2 key keeps the private key on a hardware device and needs a physical touch for each login, which makes it phishing-resistant. TOTP uses a code you type, which is weaker because it can be entered into a fake prompt, but works anywhere and needs no hardware.
Which OpenSSH version supports hardware security keys?
OpenSSH 8.2 added support for FIDO/U2F hardware authenticators with the ecdsa-sk and ed25519-sk key types. The built-in middleware requires libfido2, and both the client and server need to support these key types.
How do I avoid locking myself out when enabling 2FA?
Keep an existing session open, validate the config with sshd -t, test a new connection from a separate terminal before closing anything, and make sure you have a provider console or rescue mode plus scratch codes or a spare key.
What does AuthenticationMethods publickey,keyboard-interactive do?
It requires the user to pass public key authentication first and then keyboard-interactive authentication, which with PAM is the TOTP prompt. Each listed method must also be enabled in the configuration.
Is TOTP on SSH still worth it if I use fail2ban?
Yes. fail2ban slows guessing, but it does nothing about a stolen key or a captured credential. A second factor protects against exactly that, so they cover different risks.
Not sure where your server stands?
Get a free, read-only security check, or talk to a security engineer about managed protection.
Get your free server check Talk to a security engineer