SSH & ACCESS
June 24, 2026

How to Set Up Two-Factor Authentication for SSH

10 min read
Author
CloudStick Team
WordPress Engineer
Share this article
Two-Factor Authentication for SSH
CloudStick
Two-Factor Auth for SSH

Why 2FA for SSH?

SSH keys are strong authentication, but a stolen or leaked private key still grants full access to your server. Two-factor authentication (2FA) adds a time-based one-time password (TOTP) as a second factor — even with the key file, an attacker cannot log in without the rotating 6-digit code from an authenticator app.

This means your private key can be compromised — copied from a laptop, found in a backup, or leaked in a credential dump — and your server remains protected. The attacker would additionally need real-time access to your authenticator app (Google Authenticator, Authy, or 1Password) to generate the correct TOTP code. This article covers setting up TOTP 2FA using libpam-google-authenticator on Ubuntu 24.04.

Install Google Authenticator

Install the PAM module and run the setup wizard for each user who will use 2FA. Run the wizard as the target user — not as root — so the ~/.google_authenticator file is created in that user's home directory:

# Install the PAM module
sudo apt install libpam-google-authenticator -y
# Run the setup wizard for the user who will use 2FA
# DO NOT run as root for root's 2FA — run as the target user
google-authenticator
# The wizard asks:
# Make tokens time-based? → y
# Update .google_authenticator file? → y
# Disallow multiple uses? → y
# Allow 30 second window tolerance? → y
# Enable rate limiting? → y
# Scan the QR code with your authenticator app
# Note the emergency scratch codes — store them safely

The wizard generates a ~/.google_authenticator file containing the TOTP secret and configuration. Protect it immediately: chmod 400 ~/.google_authenticator. This file is the seed for all future TOTP codes — anyone who reads it can generate valid codes.

Configure PAM

PAM (Pluggable Authentication Modules) controls what authentication steps SSH applies. Edit the SSH PAM config to require the TOTP code as part of the login flow:

sudo nano /etc/pam.d/sshd
# Add this line BEFORE @include common-auth:
auth required pam_google_authenticator.so nullok
# The 'nullok' option allows users without 2FA configured to still log in
# Remove 'nullok' when all users have 2FA set up

Also comment out @include common-auth if you want to disable PAM's standard password fallback — otherwise PAM may still ask for a password AND the TOTP code, which is redundant when using SSH key authentication. The goal is: SSH key + TOTP, with no password prompt at all.

Update sshd_config

The SSH daemon must be told to require both public key authentication and keyboard-interactive (which is how PAM delivers the TOTP prompt). Open /etc/ssh/sshd_config and set the following:

sudo nano /etc/ssh/sshd_config
# Set these:
ChallengeResponseAuthentication yes
UsePAM yes
AuthenticationMethods publickey,keyboard-interactive
# The 'publickey,keyboard-interactive' means:
# Step 1: SSH key authentication
# Step 2: TOTP code via keyboard-interactive
# Test config:
sudo sshd -t
sudo systemctl reload sshd

The AuthenticationMethods publickey,keyboard-interactive directive enforces both factors in sequence. A connection that passes only the key check but fails the TOTP step is rejected. Always run sudo sshd -t to validate your config file before reloading — a syntax error in sshd_config can prevent the service from starting.

Test and Verify

Open a new terminal session — do not close your existing one yet. Connect to the server. You should see a two-step prompt flow:

# First prompt: SSH key passphrase (if your key has one)
Enter passphrase for key '~/.ssh/id_ed25519':
# Second prompt: TOTP code from your authenticator app
Verification code:
# Enter the current 6-digit code from your app
# The code rotates every 30 seconds — enter it before it expires
# If both succeed, you land at the shell prompt:
user@your-server:~$
Keep your emergency scratch codes. If you lose your phone and don't have the scratch codes, you will be permanently locked out. Store them in a password manager, not on the server.

Do not close your existing session until the new session confirms the full 2FA flow works end-to-end. The scratch codes generated during setup bypass TOTP entirely — they are your only recovery path if you lose your phone. Treat them like a root password: store them in a password manager, not in a note on the server itself.

CloudStick Security

CloudStick's server management doesn't require you to have 2FA configured on SSH — it manages servers via its own secure agent connection. The CloudStick agent communicates over an outbound channel from your server to the CloudStick backend, so the dashboard never needs to connect through your user's SSH keys.

However, for servers you connect to directly via SSH — for maintenance, debugging, or deployments — 2FA adds meaningful protection, especially on shared developer accounts where multiple people may hold copies of the same key. CloudStick's built-in browser terminal connects over its agent channel, not via your user's SSH keys, so 2FA on SSH doesn't affect CloudStick dashboard access in any way.

Leave a comment
Full Name
Email Address
Message
On this page

We use cookies to improve your experience

CloudStick uses cookies to personalise content, analyse traffic and keep you signed in. Cookie Policy · Terms of Service

Manage cookies