Add an extra layer of security to SSH logins by requiring both a password and a time-based one-time password (TOTP) generated by Google Authenticator or compatible apps.
Prerequisites
- Root or sudo access
- Smartphone with authenticator app
- Active SSH connection for testing
What this solves
SSH password-only authentication is vulnerable to brute force attacks and credential theft. Two-factor authentication (2FA) adds a second authentication layer using time-based one-time passwords (TOTP) from your smartphone. Even if attackers obtain your password, they cannot access your server without the constantly changing 6-digit code from your authenticator app.
Step-by-step configuration
Update system packages
Start by updating your system to ensure you have the latest security patches and package repositories.
sudo apt update && sudo apt upgrade -y
Install Google Authenticator PAM module
The Google Authenticator PAM module integrates TOTP authentication with the Linux authentication system.
sudo apt install -y libpam-google-authenticator
Configure PAM for SSH authentication
Edit the PAM configuration to require Google Authenticator for SSH logins. This adds the TOTP requirement to the authentication chain.
sudo nano /etc/pam.d/sshd
Add this line at the top of the file, right after the initial comments:
auth required pam_google_authenticator.so
required keyword means SSH login will fail if TOTP verification fails. Use sufficient instead if you want to allow fallback to password-only authentication during testing.Configure SSH daemon for 2FA
Modify the SSH daemon configuration to enable both password authentication and challenge-response authentication required for 2FA.
sudo nano /etc/ssh/sshd_config
Find and modify these lines in the SSH configuration:
ChallengeResponseAuthentication yes
PasswordAuthentication yes
AuthenticationMethods password,keyboard-interactive
If any of these lines don't exist, add them. The AuthenticationMethods line requires both a password and the TOTP code.
Restart SSH service
Apply the SSH configuration changes by restarting the SSH daemon.
sudo systemctl restart sshd
sudo systemctl status sshd
Set up TOTP for your user account
Run the Google Authenticator setup for your user account. This generates the secret key and QR code for your smartphone app.
google-authenticator
Answer the prompts as follows:
- "Do you want authentication tokens to be time-based?" → Yes (Y)
- "Do you want to disallow multiple uses of the same authentication token?" → Yes (Y)
- "By default, tokens are good for 30 seconds..." → No (N) for better security
- "Do you want to enable rate-limiting?" → Yes (Y)
Scan QR code with authenticator app
The setup process displays a QR code. Scan it with Google Authenticator, Authy, or any compatible TOTP app on your smartphone. The app will start generating 6-digit codes that change every 30 seconds.
Save the backup codes displayed during setup. Store them in a secure location separate from your phone.
Set up 2FA for additional users
Each user who needs SSH access must configure their own TOTP setup. Switch to each user account and run the authenticator setup.
sudo -u username google-authenticator
Each user will get their own QR code and secret key to add to their authenticator app.
Test two-factor authentication
Open a new SSH session to test the 2FA configuration. You should be prompted for both your password and verification code.
ssh username@203.0.113.10
You'll see prompts like this:
Password: [enter your regular password]
Verification code: [enter 6-digit code from authenticator app]
The login succeeds only after providing both valid credentials.
Verify TOTP configuration files
Check that the Google Authenticator configuration was created correctly for your user.
ls -la ~/.google_authenticator
cat ~/.google_authenticator
This file contains your secret key, backup codes, and configuration options. Keep this file secure and backed up.
Advanced configuration options
Configure emergency access
Create an emergency access method in case you lose your phone. Add a backup user or configure key-based authentication for emergency access.
# Allow key-based auth for emergency user
Match User emergency
AuthenticationMethods publickey
This configuration allows the emergency user to log in with SSH keys, bypassing 2FA requirements.
Configure time synchronization
TOTP requires accurate time synchronization. Install and configure NTP to prevent authentication failures due to time drift.
sudo apt install -y chrony
sudo systemctl enable --now chrony
Verify your setup
# Check SSH service status
sudo systemctl status sshd
Verify PAM configuration
grep google_authenticator /etc/pam.d/sshd
Check SSH configuration
grep -E "ChallengeResponse|AuthenticationMethods" /etc/ssh/sshd_config
Verify user TOTP setup
ls -la ~/.google_authenticator
Check time synchronization
timedatectl status
Common issues
| Symptom | Cause | Fix |
|---|---|---|
| "Verification code" prompt not appearing | SSH config not properly set | Check ChallengeResponseAuthentication yes in sshd_config |
| Code rejected as invalid | Time synchronization issue | Install and configure chrony/ntp on server |
| Cannot access server at all | PAM misconfiguration | Boot from rescue console, edit /etc/pam.d/sshd |
| QR code not scanning | Terminal display issues | Use the manual secret key displayed below QR code |
| Lost phone with authenticator | No backup access method | Use backup codes saved during setup or console access |
| Multiple failed attempts | Rate limiting enabled | Wait 5 minutes or check ~/.google_authenticator settings |
Security considerations
Store backup codes in a secure location separate from your authenticator device. Consider using multiple devices or backup authentication methods for critical servers. Regular monitoring of SSH logs helps detect unauthorized access attempts.
For enhanced security, combine 2FA with SSH key authentication and consider implementing automated intrusion prevention for comprehensive protection.
Next steps
- Configure SSH key authentication and security hardening
- Set up advanced firewall rules for SSH protection
- Configure centralized logging for SSH audit trails
- Implement SSH bastion host for secure network access
Running this in production?
Automated install script
Run this to automate the entire setup
#!/usr/bin/env bash
set -euo pipefail
# SSH Two-Factor Authentication Setup Script with Google Authenticator
# Production-grade installer for Ubuntu, Debian, AlmaLinux, Rocky Linux, CentOS
readonly SCRIPT_NAME=$(basename "$0")
readonly LOG_FILE="/tmp/ssh-2fa-setup.log"
# Colors
readonly RED='\033[0;31m'
readonly GREEN='\033[0;32m'
readonly YELLOW='\033[1;33m'
readonly BLUE='\033[0;34m'
readonly NC='\033[0m'
# Logging function
log() {
echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a "$LOG_FILE"
}
# Output functions
success() { echo -e "${GREEN}✓ $1${NC}" | tee -a "$LOG_FILE"; }
error() { echo -e "${RED}✗ $1${NC}" | tee -a "$LOG_FILE"; }
warning() { echo -e "${YELLOW}⚠ $1${NC}" | tee -a "$LOG_FILE"; }
info() { echo -e "${BLUE}ℹ $1${NC}" | tee -a "$LOG_FILE"; }
# Usage function
usage() {
cat << EOF
Usage: $SCRIPT_NAME [OPTIONS]
Configure SSH two-factor authentication with Google Authenticator TOTP
OPTIONS:
-u USER Username to configure 2FA for (default: current user)
-h Show this help message
EXAMPLES:
$SCRIPT_NAME # Setup for current user
$SCRIPT_NAME -u alice # Setup for user alice
This script will:
1. Install Google Authenticator PAM module
2. Configure PAM and SSH for 2FA
3. Set up TOTP for specified user
4. Test the configuration
WARNING: Keep your current SSH session open during setup!
EOF
}
# Cleanup function
cleanup() {
local exit_code=$?
if [ $exit_code -ne 0 ]; then
error "Script failed. Check log: $LOG_FILE"
warning "Your SSH configuration may be partially modified"
warning "Keep current session open and check /etc/ssh/sshd_config"
fi
}
# Set up error handling
trap cleanup ERR EXIT
# Parse arguments
TARGET_USER=""
while getopts "u:h" opt; do
case $opt in
u) TARGET_USER="$OPTARG" ;;
h) usage; exit 0 ;;
\?) error "Invalid option: -$OPTARG"; usage; exit 1 ;;
esac
done
# Set default user if not specified
if [ -z "$TARGET_USER" ]; then
TARGET_USER="${SUDO_USER:-$(whoami)}"
fi
# Validate user exists
if ! id "$TARGET_USER" &>/dev/null; then
error "User $TARGET_USER does not exist"
exit 1
fi
# Check prerequisites
if [ "$EUID" -ne 0 ]; then
error "This script must be run as root or with sudo"
exit 1
fi
# Auto-detect distribution
if [ ! -f /etc/os-release ]; then
error "Cannot detect distribution - /etc/os-release not found"
exit 1
fi
. /etc/os-release
case "$ID" in
ubuntu|debian)
PKG_MGR="apt"
PKG_UPDATE="apt update -qq"
PKG_INSTALL="apt install -y"
PKG_2FA="libpam-google-authenticator"
;;
almalinux|rocky|centos|rhel|ol)
PKG_MGR="dnf"
PKG_UPDATE="dnf update -y -q"
PKG_INSTALL="dnf install -y"
PKG_2FA="google-authenticator"
;;
fedora)
PKG_MGR="dnf"
PKG_UPDATE="dnf update -y -q"
PKG_INSTALL="dnf install -y"
PKG_2FA="google-authenticator"
;;
amzn)
PKG_MGR="yum"
PKG_UPDATE="yum update -y -q"
PKG_INSTALL="yum install -y"
PKG_2FA="google-authenticator"
;;
*)
error "Unsupported distribution: $ID"
exit 1
;;
esac
info "Starting SSH 2FA setup for $TARGET_USER on $PRETTY_NAME"
log "Package manager: $PKG_MGR"
# Step 1: Update system packages
echo -e "\n[1/7] Updating system packages..."
$PKG_UPDATE >/dev/null 2>&1
success "System packages updated"
# Step 2: Install Google Authenticator PAM module
echo -e "\n[2/7] Installing Google Authenticator PAM module..."
$PKG_INSTALL "$PKG_2FA" >/dev/null 2>&1
success "Google Authenticator installed"
# Step 3: Backup existing configurations
echo -e "\n[3/7] Backing up configurations..."
cp /etc/pam.d/sshd /etc/pam.d/sshd.backup.$(date +%Y%m%d_%H%M%S)
cp /etc/ssh/sshd_config /etc/ssh/sshd_config.backup.$(date +%Y%m%d_%H%M%S)
success "Configuration backups created"
# Step 4: Configure PAM for SSH authentication
echo -e "\n[4/7] Configuring PAM for SSH authentication..."
if ! grep -q "pam_google_authenticator.so" /etc/pam.d/sshd; then
sed -i '/^@include common-auth/i auth required pam_google_authenticator.so' /etc/pam.d/sshd 2>/dev/null || \
sed -i '1a auth required pam_google_authenticator.so' /etc/pam.d/sshd
success "PAM configuration updated"
else
warning "PAM already configured for Google Authenticator"
fi
# Step 5: Configure SSH daemon for 2FA
echo -e "\n[5/7] Configuring SSH daemon..."
SSHD_CONFIG="/etc/ssh/sshd_config"
# Update ChallengeResponseAuthentication
if grep -q "^ChallengeResponseAuthentication" "$SSHD_CONFIG"; then
sed -i 's/^ChallengeResponseAuthentication.*/ChallengeResponseAuthentication yes/' "$SSHD_CONFIG"
else
echo "ChallengeResponseAuthentication yes" >> "$SSHD_CONFIG"
fi
# Update PasswordAuthentication
if grep -q "^PasswordAuthentication" "$SSHD_CONFIG"; then
sed -i 's/^PasswordAuthentication.*/PasswordAuthentication yes/' "$SSHD_CONFIG"
else
echo "PasswordAuthentication yes" >> "$SSHD_CONFIG"
fi
# Add AuthenticationMethods
if ! grep -q "^AuthenticationMethods" "$SSHD_CONFIG"; then
echo "AuthenticationMethods password,keyboard-interactive" >> "$SSHD_CONFIG"
fi
# Test SSH configuration
if sshd -t; then
success "SSH configuration is valid"
else
error "SSH configuration test failed"
exit 1
fi
# Step 6: Restart SSH service
echo -e "\n[6/7] Restarting SSH service..."
systemctl restart sshd
if systemctl is-active sshd >/dev/null; then
success "SSH service restarted successfully"
else
error "SSH service failed to start"
exit 1
fi
# Step 7: Set up TOTP for user account
echo -e "\n[7/7] Setting up TOTP for user $TARGET_USER..."
# Create setup script for user
SETUP_SCRIPT="/tmp/ga_setup_${TARGET_USER}.sh"
cat > "$SETUP_SCRIPT" << 'EOF'
#!/bin/bash
google-authenticator --time-based --disallow-reuse --force --rate-limit=3 --rate-time=30 --window-size=3
EOF
chmod 755 "$SETUP_SCRIPT"
chown "$TARGET_USER:$(id -gn "$TARGET_USER")" "$SETUP_SCRIPT"
info "Running Google Authenticator setup for $TARGET_USER..."
info "Please scan the QR code with your authenticator app"
warning "Save the backup codes in a secure location!"
# Run setup as the target user
if sudo -u "$TARGET_USER" "$SETUP_SCRIPT"; then
success "Google Authenticator setup completed for $TARGET_USER"
else
error "Google Authenticator setup failed"
exit 1
fi
# Clean up setup script
rm -f "$SETUP_SCRIPT"
# Set proper permissions on authenticator file
USER_HOME=$(getent passwd "$TARGET_USER" | cut -d: -f6)
if [ -f "$USER_HOME/.google_authenticator" ]; then
chown "$TARGET_USER:$(id -gn "$TARGET_USER")" "$USER_HOME/.google_authenticator"
chmod 600 "$USER_HOME/.google_authenticator"
fi
# Final verification
echo -e "\n${BLUE}=== Configuration Verification ===${NC}"
# Check PAM configuration
if grep -q "pam_google_authenticator.so" /etc/pam.d/sshd; then
success "PAM configuration: OK"
else
error "PAM configuration: FAILED"
fi
# Check SSH configuration
if grep -q "ChallengeResponseAuthentication yes" /etc/ssh/sshd_config && \
grep -q "AuthenticationMethods.*keyboard-interactive" /etc/ssh/sshd_config; then
success "SSH configuration: OK"
else
error "SSH configuration: FAILED"
fi
# Check SSH service
if systemctl is-active sshd >/dev/null; then
success "SSH service: ACTIVE"
else
error "SSH service: INACTIVE"
fi
# Check user setup
if [ -f "$USER_HOME/.google_authenticator" ]; then
success "User $TARGET_USER TOTP: CONFIGURED"
else
error "User $TARGET_USER TOTP: NOT CONFIGURED"
fi
echo -e "\n${GREEN}=== Setup Complete! ===${NC}"
warning "IMPORTANT: Keep this SSH session open!"
info "Test 2FA in a NEW terminal with: ssh $TARGET_USER@$(hostname -I | awk '{print $1}')"
info "You will be prompted for:"
info " 1. Your password"
info " 2. Verification code from your authenticator app"
info ""
info "Backup codes and logs saved to: $LOG_FILE"
warning "If you get locked out, use console access to restore from backups:"
info " sudo cp /etc/ssh/sshd_config.backup.* /etc/ssh/sshd_config"
info " sudo cp /etc/pam.d/sshd.backup.* /etc/pam.d/sshd"
info " sudo systemctl restart sshd"
Review the script before running. Execute with: bash install.sh