How to Add Two-Factor Authentication to SSH
Add a time-based one-time code on top of your SSH key for defence in depth. Set up Google Authenticator PAM without locking yourself out.
For high-value servers, an SSH key alone may not be enough โ if the key leaks, it's game over. Two-factor authentication (2FA) adds a rotating six-digit code from your phone, so an attacker needs both your key and your device. This guide sets up TOTP-based 2FA using the Google Authenticator PAM module.
How SSH 2FA works
You'll combine two factors: something you have (your SSH key) and something you generate (a time-based one-time password, or TOTP). The server runs a PAM module that checks the code from an authenticator app like Google Authenticator, Authy, or Aegis.
Step 1: install the PAM module
On Ubuntu/Debian:
sudo apt update
sudo apt install libpam-google-authenticator -y
Step 2: run the setup wizard as your user
Run the tool as the user who will log in โ not root. It generates a secret and QR code:
google-authenticator
Answer the prompts:
- Time-based tokens: yes
- Scan the QR code into your authenticator app
- Save the emergency scratch codes somewhere safe โ they're your backup if you lose your phone
- Update the
.google_authenticatorfile: yes - Disallow multiple uses: yes
- Rate limiting: yes
{{SCREENSHOT}}
Step 3: tell PAM to require the code
Edit /etc/pam.d/sshd. Add this line at the top:
auth required pam_google_authenticator.so
To require a code only after key auth (recommended), you'll also adjust sshd_config below. If you want to keep password + code working for accounts without 2FA set up, add nullok to the line: auth required pam_google_authenticator.so nullok.
Step 4: configure sshd to demand both factors
Edit /etc/ssh/sshd_config:
KbdInteractiveAuthentication yes
AuthenticationMethods publickey,keyboard-interactive
UsePAM yes
AuthenticationMethods publickey,keyboard-interactive means a user must present a valid key first, then a valid TOTP code. This is the strongest common setup.
Step 5: validate and reload carefully
Keep your current session open. Validate then reload:
sudo sshd -t && sudo systemctl reload ssh
Open a new terminal and log in. You should authenticate with your key, then be prompted for a verification code. Only after this works should you close your original session.
Don't lock out automation
Automated deploys and CI can't type a TOTP code. Exempt them by IP or user. For example, skip 2FA for connections from your CI's IP by wrapping the PAM line with pam_access, or keep a dedicated key-only user restricted by firewall. Plan this before you roll 2FA out fleet-wide.
Layer it with the rest
2FA sits on top of the fundamentals. Make sure you've already done SSH key auth, disabled root login, and set up fail2ban and a firewall.
Hardware keys: an even stronger second factor
TOTP codes are excellent, but a hardware security key (a YubiKey or any FIDO2/U2F device) is stronger still, because the secret never leaves the physical device and there's nothing to phish. Modern OpenSSH supports this natively through ed25519-sk keys:
ssh-keygen -t ed25519-sk -C "yubikey"
You'll be asked to touch the key to confirm. The resulting key requires the physical device present to authenticate, so a stolen private key file alone is useless. For servers you truly care about, a hardware key as the "something you have" factor is the gold standard, and it removes the friction of typing a code on every login.
Planning for scale
Setting up 2FA on one server is simple; rolling it across a fleet needs a plan. A few things to decide up front:
- Which accounts need it. Human logins, yes. Service and deploy accounts usually can't type a code, so exempt them by user or restrict them tightly by firewall and key instead.
- Backup access. Store the emergency scratch codes somewhere central and secure, and make sure at least one administrator can reach the provider's console to recover a locked-out box.
- Onboarding. New team members run
google-authenticatoronce as their user; document it so it isn't a mystery.
Treat 2FA as one layer among several. It dramatically raises the cost of a stolen key, but it works best on top of key-only auth, no root login, a firewall, and fail2ban โ defence in depth, not a single magic switch. Reviewed together, those layers make a compromise require several independent failures at once.
FAQ
What happens if I lose my phone?
Use one of the emergency scratch codes you saved during setup, or log in via your host's web console and remove the ~/.google_authenticator file. Always store the scratch codes offline.
Does 2FA slow down every login?
It adds one six-digit code per new session. With an SSH agent and connection multiplexing, the friction is minimal for day-to-day work.
Can I require 2FA only for some users?
Yes. With nullok in the PAM config, users without a configured secret skip the code, while users who ran google-authenticator must provide one.
Is TOTP better than SMS codes?
Yes. TOTP apps work offline and aren't vulnerable to SIM-swapping or intercepted texts. Avoid SMS-based 2FA where possible.
Rollout checklist
Enabling 2FA safely comes down to a handful of careful steps:
- Install
libpam-google-authenticatorand rungoogle-authenticatoras your user (not root). - Save the emergency scratch codes offline โ they're your way back in if you lose your phone.
- Add
auth required pam_google_authenticator.soto/etc/pam.d/sshd. - Set
AuthenticationMethods publickey,keyboard-interactiveinsshd_configto require key *and* code. - Validate with
sudo sshd -t, reload, and confirm the two-step login in a fresh session before closing your working one. - Exempt automation and CI accounts that can't type a code, by user or firewall.
The recurring theme is the same as every SSH change: keep a working session open, test the new path fully, and know your console fallback exists. Do that, and adding a second factor carries no risk of locking yourself out while dramatically raising the bar for anyone who steals a key.
Two-factor SSH turns a stolen key into a dead end. Build it on a VPS where you control the whole stack โ see Nxeon VPS hosting and our security overview.