Security

Add two-factor authentication (2FA) to SSH on Linux

Protect SSH logins with time-based one-time codes using the Google Authenticator PAM module, combined with SSH keys, without locking yourself out.

AdvancedLinux VPS3 min read

SSH two-factor authentication asks for a 6-digit code from an authenticator app in addition to your key or password. Even if a private key leaks, an attacker cannot log in without your phone. This guide uses the widely packaged Google Authenticator PAM module, which works with any TOTP app (Google Authenticator, Authy, Aegis, 1Password, Bitwarden).

Before you start

  • Root access to an Ubuntu/Debian or AlmaLinux/Rocky VPS.
  • SSH key login already working for your user.
  • An authenticator app on your phone.
  • Console/VNC access from your provider and a second SSH session kept open while you test.
  • Accurate server time: TOTP depends on it. Check timedatectl shows System clock synchronized: yes.

Install the PAM module

Ubuntu/Debian:

apt install libpam-google-authenticator

AlmaLinux/Rocky (EPEL):

dnf install epel-release && dnf install google-authenticator qrencode

Enrol your user

Run as the user who will log in (not with sudo):

google-authenticator -t -d -f -r 3 -R 30 -w 3

This creates time-based tokens, disallows code reuse, limits attempts to 3 per 30 seconds and allows a small clock drift. Scan the QR code in your app and save the emergency scratch codes in your password manager. Each works once if you lose your phone. The secret is stored in ~/.google_authenticator.

Configure PAM for SSH

Edit /etc/pam.d/sshd and add at the end:

auth required pam_google_authenticator.so nullok

nullok lets users who have not enrolled yet log in without a code; remove it once every user is enrolled. If you use keys only (recommended), also comment out the password prompt so users are not asked for a password after their key:

#@include common-auth

On AlmaLinux/Rocky the equivalent line is auth substack password-auth.

Configure sshd

In /etc/ssh/sshd_config (and check /etc/ssh/sshd_config.d/ for overrides):

KbdInteractiveAuthentication yes
UsePAM yes
AuthenticationMethods publickey,keyboard-interactive

On older OpenSSH versions the first option is called ChallengeResponseAuthentication yes. AuthenticationMethods requires a valid key and the code. Test and reload:

sshd -t && systemctl reload ssh || systemctl reload sshd

Warning: keep your existing session open. Test a new login in a separate terminal before closing anything.

Test the login

ssh user@203.0.113.10

After the key is accepted you should see Verification code:. Enter the current code from your app.

Exempt automation accounts

Deploy users and backup jobs cannot type codes. Exempt them with a Match block at the end of sshd_config:

Match User deploy
    AuthenticationMethods publickey

Keep such accounts limited (key only, narrow sudo, restricted source IPs if possible).

Troubleshooting

  • Code always rejected: server or phone time is off. Sync with timedatectl set-ntp true.
  • Not asked for a code: KbdInteractiveAuthentication is off, AuthenticationMethods is missing, or an override file in sshd_config.d wins.
  • Asked for password and code: the common-auth / password-auth line is still active in /etc/pam.d/sshd.
  • Lost phone: use a scratch code, then rerun google-authenticator to enrol a new device. If all else fails, log in through the provider console and remove the PAM line temporarily.

SSH 2FA FAQ

Is 2FA needed if I already use SSH keys?

Keys are strong on their own. 2FA adds protection if a key is stolen from a laptop or backup, which matters for servers holding customer data.

Can I use a hardware key instead?

Yes. OpenSSH 8.2+ supports FIDO2 keys directly with ssh-keygen -t ed25519-sk, which needs no PAM setup.

Does cPanel support 2FA?

Yes, cPanel and WHM have their own two-factor authentication for the web login, separate from SSH.

Next steps

Was this guide helpful?

Still stuck?

Ask MdmHost AI for a quick answer, or open a ticket and our team will take a look. We are here 24/7.