Implement two-factor authentication for SSH with Google Authenticator and TOTP

Intermediate 25 min Apr 19, 2026 149 views
Ubuntu 24.04 Debian 12 AlmaLinux 9 Rocky Linux 9

Secure your SSH connections by adding TOTP-based two-factor authentication using Google Authenticator and PAM modules for an additional layer of protection beyond passwords and keys.

Prerequisites

  • Root or sudo access to the server
  • Smartphone with authenticator app
  • Basic SSH access configured

What this solves

SSH two-factor authentication adds an extra security layer by requiring both your password/key and a time-based token from your phone. This prevents unauthorized access even if your SSH credentials are compromised, making it essential for servers handling sensitive data or exposed to the internet.

Step-by-step configuration

Update system packages

Start by updating your package manager to ensure you get the latest security updates.

sudo apt update && sudo apt upgrade -y
sudo dnf update -y

Install Google Authenticator PAM module

Install the PAM module that handles TOTP token validation for SSH authentication.

sudo apt install -y libpam-google-authenticator qrencode
sudo dnf install -y google-authenticator qrencode

Generate TOTP secret for your user

Run the Google Authenticator setup as the user who will use SSH 2FA. This creates a secret key and QR code.

google-authenticator

Answer the prompts as follows:

  • "Do you want authentication tokens to be time-based?" → y
  • "Do you want me to update your "/home/username/.google_authenticator" file?" → y
  • "Do you want to disallow multiple uses of the same authentication token?" → y
  • "By default, tokens are good for 30 seconds..." → n
  • "Do you want to enable rate-limiting?" → y
Important: Save the emergency scratch codes displayed. You'll need them if your phone is unavailable.

Scan QR code with authenticator app

Use your smartphone's authenticator app to scan the QR code displayed in the terminal. Popular options include Google Authenticator, Authy, or Microsoft Authenticator.

The QR code contains your secret key and server information. Your app will start generating 6-digit codes that change every 30 seconds.

Configure PAM for SSH authentication

Add the Google Authenticator PAM module to SSH authentication. Edit the SSH PAM configuration file.

sudo nano /etc/pam.d/sshd

Add this line at the top of the file, before other auth lines:

auth required pam_google_authenticator.so
Warning: Test this configuration before logging out. Keep a second SSH session open until you verify 2FA is working.

Configure SSH daemon for 2FA

Modify the SSH daemon configuration to enable both password/key authentication and challenge-response authentication.

sudo nano /etc/ssh/sshd_config

Find and modify these lines (or add them if they don't exist):

ChallengeResponseAuthentication yes
AuthenticationMethods publickey,keyboard-interactive
UsePAM yes

For password-based authentication with 2FA, use this instead:

ChallengeResponseAuthentication yes
AuthenticationMethods password,keyboard-interactive
UsePAM yes
PasswordAuthentication yes

Restart SSH service

Restart the SSH daemon to apply the configuration changes.

sudo systemctl restart sshd
sudo systemctl status sshd

Verify the service restarted without errors before proceeding.

Set up 2FA for additional users

Each user who needs SSH 2FA must run the google-authenticator setup individually. Switch to each user account and repeat the setup process.

sudo su - username
google-authenticator

Each user gets their own secret key and emergency codes. Users cannot share TOTP tokens.

Test SSH 2FA login

Test the two-factor authentication from a different terminal or machine. Keep your current SSH session open as backup.

ssh username@your-server-ip

You should see prompts for:

  1. Your SSH key passphrase (if using key-based auth) or password
  2. "Verification code:" prompt for your 6-digit TOTP code

Enter the current code from your authenticator app. The login should succeed only with both credentials.

Verify your setup

Check that SSH 2FA is working correctly with these verification steps:

# Check SSH daemon configuration
sudo sshd -T | grep -E 'challengeresponseauthentication|authenticationmethods|usepam'

Verify PAM configuration

grep google_authenticator /etc/pam.d/sshd

Check user's 2FA file exists

ls -la ~/.google_authenticator

Test authentication in dry-run mode

sudo ssh -o PreferredAuthentications=keyboard-interactive username@localhost

Common issues

SymptomCauseFix
"Verification code:" never appearsChallengeResponseAuthentication disabledSet ChallengeResponseAuthentication yes in sshd_config
Codes always rejectedServer time driftSync time with sudo ntpdate -s time.nist.gov
Locked out completelyNo emergency codes savedRemove ~/.google_authenticator via console access
Only asks for passwordAuthenticationMethods not setAdd AuthenticationMethods line to sshd_config
PAM authentication failureWrong PAM module pathVerify pam_google_authenticator.so exists in /lib/security/
SSH key bypasses 2FASingle auth method configuredUse AuthenticationMethods publickey,keyboard-interactive

Configure backup access methods

Set up emergency access to prevent lockouts when 2FA fails.

Save emergency scratch codes

During initial setup, Google Authenticator provides emergency scratch codes. Store these securely offline.

# View existing codes (if you missed them)
head -1 ~/.google_authenticator

Configure console access exemption

Allow local console login without 2FA for emergency access. Edit the PAM configuration:

sudo nano /etc/pam.d/sshd

Modify the Google Authenticator line to skip 2FA for local connections:

auth [success=1 default=ignore] pam_access.so accessfile=/etc/security/access-local.conf
auth required pam_google_authenticator.so

Create backup user account

Maintain one admin account without 2FA for emergencies, accessible only from specific IP addresses.

sudo useradd -m -s /bin/bash emergency-admin
sudo usermod -aG sudo emergency-admin
sudo passwd emergency-admin

Restrict this account in /etc/ssh/sshd_config:

Match User emergency-admin
    AuthenticationMethods password
    AllowUsers emergency-admin
    PermitRootLogin no

Advanced configuration options

Customize token validation window

Adjust time tolerance for TOTP codes to handle clock drift. Edit each user's configuration:

nano ~/.google_authenticator

Add these options to the first line after the secret key:

# Add to secret key line: " WINDOW_SIZE=3

This allows codes from 1.5 minutes before/after current time

Enable grace login period

Allow a grace period for new 2FA setups. Modify the PAM line:

auth required pam_google_authenticator.so grace_period=86400

This gives users 24 hours to set up their authenticator apps after their first login.

Configure per-user 2FA settings

Create a script to batch-configure 2FA for multiple users with consistent settings:

#!/bin/bash
USER=$1
if [ -z "$USER" ]; then
    echo "Usage: $0 username"
    exit 1
fi

sudo -u $USER google-authenticator --time-based --disallow-reuse --force --rate-limit=3 --rate-time=30 --window-size=3
echo "2FA setup complete for user: $USER"
echo "Emergency codes saved to /home/$USER/.google_authenticator"
sudo chmod +x /usr/local/bin/setup-user-2fa.sh
sudo /usr/local/bin/setup-user-2fa.sh newuser

Monitor and maintain 2FA

Set up monitoring to track 2FA usage and failures:

# Monitor SSH authentication attempts
sudo tail -f /var/log/auth.log | grep sshd

Check for 2FA-related errors

sudo grep "google_authenticator" /var/log/auth.log

Monitor failed login attempts

sudo grep "Failed password" /var/log/auth.log

Consider integrating with existing monitoring systems to alert on repeated 2FA failures, which may indicate attack attempts.

Next steps

Running this in production?

Want this handled for you? Setting up SSH 2FA once is straightforward. Keeping it patched, monitored, backed up and tuned across environments is the harder part. See how we run infrastructure like this for European SaaS and e-commerce teams.

Automated install script

Run this to automate the entire setup

Need help?

Don't want to manage this yourself?

We handle infrastructure security hardening for businesses that depend on uptime. From initial setup to ongoing operations.