Linux Security·7 min read·

Adding 2FA for SSH Logins with PAM, TOTP, and U2F

Require a second factor for SSH with TOTP via PAM or a hardware U2F key, without breaking key-only automation or locking out legitimate users.

NB

Netbay Infrastructure Team

Netbay Engineering

On this page

A stolen SSH key is only as dangerous as the single factor protecting it. Two-factor authentication for SSH raises that bar: even with your private key copied, an attacker still needs a code from your phone or a tap on your hardware key. This guide shows how to add TOTP (time-based one-time passwords) and U2F/FIDO2 to SSH on a Linux VPS using PAM, and how to keep it from breaking the automation that legitimately relies on key-only connections.

Two-factor SSH: key plus code FACTOR 1 SSH private key actually accepted pubkey auth FACTOR 2 TOTP six-digit code or U2F tap PAM verified SESSION Both factors must succeed before the shell opens

Understand What 2FA Changes for SSH

Classic SSH uses a single factor: possession of the private key. PAM lets you chain a second factor on top, requiring the key to authenticate and then a TOTP code or a hardware key tap before the session starts. The critical bit is that a key by itself no longer unlocks the door — an attacker with a copied key still fails the TOTP check. The tradeoff is on automation: any script or CI runner that connects with a key alone will now fail, so you must deliberately exempt those connections rather than disable the second factor entirely.

Set Up TOTP One-Time Passwords

TOTP is the low-friction option: no hardware to buy, just an authenticator app on your phone. Install the PAM module, generate a per-user secret, and register it in your authenticator. The secret lives in the user's home directory, so it must be permission-guarded the same way a private key is.

bash
sudo apt install -y libpam-google-authenticator
google-authenticator --time-based --issuer=netbay --window-size=3

The generator prints a QR code or secret, and the window-size setting controls how many time steps the module tolerates — a small window prevents replay attacks while a slightly larger one absorbs clock drift on the phone. Save the emergency scratch codes somewhere safe; they are your only way back in if the phone is lost. The module writes the secret to ~/.google_authenticator, which it protects automatically, but verify the mode afterward.

Wire TOTP Into the SSH Login

With the secret generated, tell PAM and sshd to require it. On Ubuntu, insert the module into the sshd PAM service and flip the challenge-response option in the SSH config. The order of the PAM lines determines the factor sequence — put the TOTP check after the authentication stacking so the key is verified first.

bash
echo "auth required pam_google_authenticator.so" | sudo tee -a /etc/pam.d/sshd
echo "ChallengeResponseAuthentication yes" | sudo tee -a /etc/ssh/sshd_config.d/2fa.conf
sudo sshd -t
sudo systemctl restart ssh

Test every step in a second, already-open session before you close the current one. The classic failure is enabling the module for all users while your automation connects by key alone — which is exactly what the next section solves by exempting those connections rather than removing the second factor for humans.

Use U2F/FIDO2 for Hardware Two-Factor

If you carry a hardware security key, U2F is stronger than TOTP because the one-time response is bound to the key itself and immune to phishing that captures a code. The pam-u2f module maps a specific hardware key to a specific user via an AuthFile, and sshd can be configured to prompt for it through its pubkey handling. The registration step pairs the key with the account, and the module check runs inside PAM the same way the TOTP module does.

bash
sudo apt install -y pam-u2f
mkdir -p ~/.config/Yubico
pamu2fcfg > ~/.config/Yubico/u2f_keys
cat /etc/pam.d/sshd

pamu2fcfg waits for you to touch the key and writes the credential mapping to u2f_keys in a home-guarded directory. Because the U2F credential is tied to physical possession of one key, a stolen private key plus a distinct stolen phone code is still useless against it. The tradeoff is that every admin must carry the registered key, and losing it without a backdoor means recovering through console access — so keep a spare or a rescue path.

Keep Automation Working by Exemption, Not Removal

The moment you enforce a second factor, scripts break. The correct approach is to keep a separate, explicitly controlled path for automation that legitimately cannot present a second factor. On the PAM side, this typically means structuring the sshd PAM stack so that an automation-only user or a separate key does not invoke the second factor, while human logins always do. Design the exemption, document it, and confirm the automation user is otherwise restricted — a passwordless automation key with an exception for 2FA is now your most sensitive credential.

An alternative that keeps everything uniform is OpenSSH's built-in FIDO2 keys: an ed25519-sk key materializes only from a tap on the security key, providing a hardware second factor intrinsically without PAM changes. That shifts the friction to the key material itself rather than a PAM rule, and it keeps automation policy in the SSH layer where it is easier to reason about.

Fallback and Recovery: Never Box Everyone Out

Two-factor that locks out your whole team is worse than no two-factor. Before enforcing anything, install the fallbacks that prevent a lockout: a scratch code sheet for bad actors, a console or out-of-band rescue path that bypasses the network SSH check, and a documented rebuild so a lost token is an annoyance, not an outage. A second session open during every change, and a test on a non-production host first, turns a risky migration into a routine one.

bash
sudo systemctl reload sshd
ssh -o PubkeyAuthentication=yes -o PreferredAuthentications=publickey deploy@host

Keeping the preferred authentication list explicit during rollout means a key-only login attempt behaves predictably, so you can verify whether your exemption actually works before you widen the enforcement.

Takeaway

Two-factor for SSH turns a copied key from a hole into a dead end: TOTP needs the phone, U2F needs the hardware, and both funnel every login through PAM deterministically. Exempt your automation deliberately instead of weakening the factor, and keep a recovery path so 2FA never boxes out your team. On a Netbay Ubuntu or Rocky VPS you control PAM and sshd from day one, so the second factor is yours to enforce immediately — netbayhosts.in.

Keep reading

Follow along on a real VPS

Deploy Linux in under 60 seconds

These guides are written against Ubuntu, Debian, and RHEL-family images — the same ones on NetBay.

Deploy an instance