Security

Enable 2FA for SSH Using Google Authenticator on Ubuntu 24.04

The first time you get locked out of your own server because an attacker brute-forced a weak password, you learn the value of layered security. Password-only SSH is a single point of failure. Adding a time-based one-time password (TOTP) via Google Authenticator turns that single password into a two-factor login: something you know (the password or key passphrase) and something you have (your phone). This guide walks through enabling 2FA for SSH on Ubuntu 24.04 using the standard PAM module. The same steps apply to Debian 12 and other systemd-based distributions.

Prerequisites

  • A Ubuntu 24.04 VPS with root or sudo access. All commands assume a non-root sudo user.
  • SSH key-based authentication already configured and tested, you need a second open session or console access in case something goes wrong during the PAM changes.
  • A smartphone with Google Authenticator, Authy, or any TOTP-compatible app installed.
  • Time synchronization enabled on the server (NTP). Run timedatectl to verify the clock is correct. TOTP codes will not match if server time drifts.

Why Add a Second Factor to SSH

SSH keys alone are strong against remote brute force, but they have downsides. A compromised private key gives an attacker full access. If you use password auth at all, even as a fallback, the exposure is obvious. Two-factor authentication addresses both scenarios. With TOTP enabled, even if someone steals your private key or guesses your password, they still need the 6-digit code from your phone to complete the login. The Google Authenticator PAM module integrates directly with the OpenSSH daemon, requiring zero changes to your key infrastructure. You keep your existing keys and simply add an interactive challenge. For a VPS holding production data or client workloads, 2FA removes the "one credential away from disaster" risk. If you rent a Linux VPS and expose SSH to the internet, this is a standard hardening step.

Step 1, Install the PAM Module

The package is libpam-google-authenticator on Debian-based systems. Install it with apt.

sudo apt update
sudo apt install libpam-google-authenticator -y

Verify the installation succeeded by checking that the PAM module is present in the library path:

ls /lib/x86_64-linux-gnu/security/pam_google_authenticator.so

You should see the file listed. If not, re-run the install and check for dependency errors.

Step 2, Configure the PAM Module for your User

Each user who wants 2FA must run the configuration tool. Log in as the target user and run:

google-authenticator

The tool is interactive. Answer each question carefully:

  • "Do you want authentication tokens to be time-based"yes. Time-based (TOTP) is the standard; counter-based is deprecated for SSH use.
  • A huge QR code and a secret key appear. Scan the QR code with your authenticator app, or manually type the secret key plus the provided URL. Your app now shows a 6-digit code that rotates every 30 seconds.
  • "Do you want me to update your "/home/user/.google_authenticator" file"yes
  • "Do you want to disallow multiple uses of the same authentication token"yes. This limits replay attacks.
  • "Do you want to increase the time skew window" → by default no. If your server clock is unreliable, type yes and allow up to 4 minutes of drift. For a properly synced VPS, leave it as no.
  • "Do you want to enable rate-limiting"yes. This limits login attempts to 3 per 30-second window, mitigating brute force via the TOTP code.

Once finished, a hidden file is created at ~/.google_authenticator. Protect it:

chmod 400 ~/.google_authenticator

The file contains the secret key and the rate-limiting configuration. Do not share it.

Step 3, Configure PAM to Require the TOTP for SSH

The PAM configuration for SSH lives in /etc/pam.d/sshd. Edit the file with your editor:

sudo nano /etc/pam.d/sshd

Add the following line at the top of the file (before the @include common-auth line):

auth required pam_google_authenticator.so

The file should look like this after the edit:

# PAM configuration for the Secure Shell service
auth required pam_google_authenticator.so
@include common-auth
... rest of the file stays unchanged ...

Why put it before common-auth? This ensures the TOTP challenge happens first. The user must provide the one-time password, and then proceeds to regular password or key authentication. You can reverse the order if you prefer key first then 2FA, both work. I prefer TOTP first because it fails fast if the user does not have the app ready.

Step 4, Configure SSH Daemon to Use Keyboard-Interactive Authentication

By default, OpenSSH may not pass interactive challenges through the PAM stack. Edit the SSH daemon configuration:

sudo nano /etc/ssh/sshd_config

Set or adjust these three parameters:

  • ChallengeResponseAuthentication yes, enables keyboard-interactive PAM challenges.
  • AuthenticationMethods publickey,keyboard-interactive, forces the user to first present a valid SSH key, then complete the TOTP challenge. This is the strongest and most common setup. If you still use password auth, replace publickey with password but consider disabling passwords entirely after the 2FA is in place.
  • UsePAM yes, already set on most Ubuntu systems, but confirm it is not commented out.

A relevant snippet from a production configuration:

# /etc/ssh/sshd_config
...
ChallengeResponseAuthentication yes
AuthenticationMethods publickey,keyboard-interactive
UsePAM yes
...

Save the file and restart the SSH service:

sudo systemctl restart sshd

Do not close your current SSH session yet. Open a second terminal window and test the new configuration before disconnecting your existing session. If the new session works, you can safely close the old one.

ParameterValuePurpose
ChallengeResponseAuthenticationyesEnables keyboard-interactive auth through PAM
AuthenticationMethodspublickey,keyboard-interactiveEnforces key + 2FA chain
UsePAMyesActivates the PAM stack for SSH

Verify the 2FA Setup

Open a new terminal and SSH into your VPS:

ssh your_user@your_vps_ip

You should see the standard key authentication proceed silently (if using key auth), followed by a prompt like:

Verification code:

Open your authenticator app, read the current 6-digit code, and type it. After a correct code, the login completes as usual. If the login succeeds without any TOTP prompt, check these common issues:

  • The AuthenticationMethods line in sshd_config is not set.
  • The ChallengeResponseAuthentication is still set to no.
  • The PAM module line is after @include common-auth and the common-auth module exits before reaching it.
  • The .google_authenticator file is owned by the wrong user or has overly restrictive permissions. It must be readable by the user that runs SSH.

Run journalctl -u sshd -n 50 to see detailed logs if the TOTP challenge does not appear or the code is rejected.

Troubleshooting Common Failures

"Verification code" prompt appears but the code is rejected

This is almost always a time sync issue. Run timedatectl on the server. If the clock is off by more than 30 seconds, install and enable NTP:

sudo apt install systemd-timesyncd -y
sudo timedatectl set-ntp true
timedatectl status

Re-run google-authenticator after syncing the clock and scan a new QR code.

SSH login hangs after key authentication

The PAM module is waiting for input but the terminal is not showing the prompt. This usually means ChallengeResponseAuthentication is still set to no. Double-check /etc/ssh/sshd_config and restart sshd.

Accidentally locked out, no working session left

Access the VPS through the out-of-band console (VNC/IPKVM) provided by your hosting provider. If you use a Vietnam VPS with KVM virtualization, the control panel typically offers a serial console. Boot into single-user mode or use the console to revert the changes in /etc/pam.d/sshd and /etc/ssh/sshd_config, then restart sshd from the console.

FAQ

Do I lose TOTP access if I lose my phone?

Yes, unless you saved the backup codes generated by google-authenticator or stored the secret key. Always write down the 5 emergency scratch codes printed during setup, each is valid once. Keep them in a password manager.

Can I use a different authenticator app?

Yes. Authy, 1Password, Bitwarden, or any standard TOTP app works. Google Authenticator is just the server module name. The one-time password follows the RFC 6238 standard.

Does 2FA work with SFTP or SCP?

SFTP and SCP use the SSH transport. If the SSH daemon is configured with AuthenticationMethods publickey,keyboard-interactive, SFTP will also require the TOTP code. Most SFTP clients can handle the interactive prompt. If you need unattended transfers, set up a separate key-only user on a non-standard port or use an SSH jump host.

Can I skip 2FA for a specific user?

Yes. In /etc/pam.d/sshd, use a conditional like auth [success=1 default=ignore] pam_succeed_if.so user notin root,deploy before the google-authenticator line to skip TOTP for listed users. This is useful for automation accounts.

Should I still use fail2ban or CrowdSec with 2FA?

Yes. 2FA protects against credential theft, but the SSH daemon still processes the connection attempt. Brute-force attacks against the TOTP prompt themselves are rate-limited by the PAM module, but a flood of authentication requests can still waste resources. Combine 2FA with fail2ban or CrowdSec for comprehensive protection.

Related articles

Note: This guide is for general reference. Every system and infrastructure has its own specifics, so test each step in a safe environment and consult a qualified engineer before applying it in production.