In today’s threat landscape, a password alone is no longer enough to protect privileged access to your servers. Multi‑factor authentication (MFA) adds a second layer of verification, dramatically reducing the risk of credential‑stuffing attacks and unauthorized SSH sessions. This guide walks you through a complete, production‑ready MFA implementation for Linux SSH logins using the Pluggable Authentication Module (PAM) and Google Authenticator. By the end, you’ll have a hardened SSH service that asks for a time‑based one‑time password (TOTP) in addition to the usual key or password.
What You’ll Need
- A Debian‑based or RHEL‑based Linux server with SSH already installed.
- Root or sudo privileges on the target machine.
- A smartphone with a TOTP app (Google Authenticator, Authy, or any compatible app).
- Internet access to install packages from your distro’s repositories.
- Basic familiarity with editing text files via
vi,nano, or similar editors.
Step 1: Install the Google Authenticator PAM Module
The first component we need is the PAM module that can generate and verify TOTP codes. On Debian/Ubuntu, the package is called libpam-google-authenticator. On CentOS/RHEL 8+ it lives in the EPEL repository.
# Debian/Ubuntu
sudo apt update && sudo apt install -y libpam-google-authenticator
# RHEL/CentOS 8+ (enable EPEL first)
sudo dnf install -y epel-release
sudo dnf install -y google-authenticator Installation is quick; the module places the binary /usr/bin/google-authenticator and the PAM library /lib/x86_64-linux-gnu/security/pam_google_authenticator.so (paths may vary slightly by distro).
Step 2: Generate a Secret for Each User
Each user that will log in via SSH needs a unique secret key. The easiest way is to run the google-authenticator command as that user. This command creates a .google_authenticator file in the user’s home directory, containing the secret, emergency scratch codes, and configuration flags.
# Switch to the target user (replace alice with the real username)
sudo -i -u alice
google-authenticator The interactive script asks a series of questions. For a typical SSH‑only setup, answer as follows:
- Do you want authentication tokens to be time‑based (y/n)? –
y - Scan the QR code? – Use your phone’s TOTP app to scan the QR code displayed in the terminal (or copy the secret manually).
- Do you want to update the .google_authenticator file? –
y - Do you want to disallow multiple uses of the same token? –
y(prevents replay attacks). - Do you want to increase the time window? –
nunless you have very high latency. - Do you want rate‑limiting? –
y(helps mitigate brute‑force attempts).
After completion you’ll see a list of eight emergency scratch codes. Store them securely; they can be used if the user loses their phone.
Step 3: Configure PAM to Use Google Authenticator
Now we tell PAM to invoke the Google Authenticator module during SSH authentication. Edit the file /etc/pam.d/sshd (or create a dedicated common-auth include, depending on your distro). Add the following line **above** any existing auth lines that use pam_unix.so:
auth required pam_google_authenticator.so nullok The nullok flag allows users without a .google_authenticator file to log in using only their password or key. If you want to enforce MFA for every SSH user, omit nullok. Save the file and exit.
Step 4: Adjust the SSH Daemon Configuration
SSH must be aware that PAM will now request an additional authentication factor. Open /etc/ssh/sshd_config and ensure the following directives are set:
# Enable PAM integration
UsePAM yes
# Allow password authentication (required for the first factor)
PasswordAuthentication yes
# If you rely solely on public‑key authentication, keep this on but remember the first factor will be the key.
PubkeyAuthentication yes
# Optional: Increase the number of authentication attempts to give users time for the second factor.
MaxAuthTries 4 After editing, test the configuration syntax:
sudo sshd -t && echo "Syntax OK" If you see Syntax OK, reload the daemon:
sudo systemctl reload sshd Step 5: Verify the Setup with a Test User
Never test MFA on your root account. Create a disposable user, set a password, and run through the flow.
sudo adduser testmfa
sudo passwd testmfa
sudo -i -u testmfa
google-authenticator # follow the same steps as in Step 2
exit Now open a new terminal (do **not** use the existing session) and attempt an SSH login:
ssh testmfa@your_server_ip You should see the usual password prompt, followed by a second prompt asking for the verification code:
Verification code: Enter the 6‑digit code from your authenticator app. If everything is correct, you’ll be logged in. If the code is wrong, SSH will reject the attempt and log a PAM failure.
Step 6: Harden the Configuration for Production
Now that the basics work, tighten a few settings to make the deployment more secure.
- Disable password authentication for key‑only users: If you enforce key‑based SSH for privileged accounts, set
PasswordAuthentication noand rely on the public key as the first factor. - Restrict PAM to MFA only: Remove
nullokfrom the PAM line to force every SSH user to have a TOTP secret. - Limit authentication attempts: In
/etc/ssh/sshd_config, setMaxAuthTries 3to reduce brute‑force windows. - Log MFA events: Add
authlog syslogtopam_google_authenticator.soif you need detailed audit trails.
After making changes, always run sshd -t and reload the daemon.
Common Mistakes to Avoid
Even seasoned admins can stumble when adding MFA to SSH. Here are the most frequent pitfalls and how to sidestep them:
- Editing the wrong PAM file: Some distributions split SSH PAM rules across
/etc/pam.d/sshdand/etc/pam.d/common-auth. Verify which file is included by checking theauthsection of/etc/pam.d/sshd. - Placing the PAM line after
pam_unix.so: The order matters. Ifpam_google_authenticator.soappears afterpam_unix.so, a successful password login can bypass the second factor. - Leaving
nullokin production: This flag is handy for testing but defeats the purpose of MFA if left enabled for all users. - Forgetting to restart/reload SSH: Changes to
sshd_configor PAM files won’t take effect until you reload the daemon. A silent typo can lock you out. - Using the same secret on multiple accounts: Each user must run
google-authenticatorindividually; copying.google_authenticatorbetween accounts creates a single point of failure.
Tips and Tricks
These optional tweaks can improve usability and security:
- Backup TOTP secrets: Export the QR code or the base‑32 secret string to a password‑protected file. In case a user loses their phone, an admin can quickly re‑enroll.
- Use hardware tokens: The same PAM module works with YubiKey OTP or FIDO2 devices when configured with
auth required pam_u2f.so. Combine with TOTP for a multi‑layered approach. - Grace period for new users: Add
auth [success=1 default=ignore] pam_unix.so nullok_securebefore the Google Authenticator line to allow a one‑time password‑only login for brand‑new accounts that haven’t set up TOTP yet. - Centralized management: For large fleets, consider deploying
.google_authenticatorvia Ansible, Chef, or Puppet. Store the secrets in an encrypted vault.
Frequently Asked Questions
Can I use MFA with public‑key authentication only?
Yes. SSH will first verify the public key, then PAM will request the TOTP code. This gives you “something you have” (the private key) plus “something you know” (the time‑based code).
What happens if the user’s phone is dead or they lose it?
Provide the eight emergency scratch codes generated during enrollment. Each code can be used once. Alternatively, an admin can reset the user’s .google_authenticator file and re‑run the enrollment process.
Is there a performance impact?
The TOTP verification is CPU‑light (a few SHA‑1/HMAC operations). In practice you’ll notice no measurable latency, even on modest VPS instances.
Conclusion
Adding multi‑factor authentication to Linux SSH logins is a relatively small configuration change that yields a massive security payoff. By leveraging the PAM Google Authenticator module, you gain a standards‑based, time‑synchronized second factor that works on any modern smartphone. Follow the steps above, double‑check the PAM order, and harden the final configuration for production. Your servers will now require both something you know (password or key) and something you have (TOTP), dramatically reducing the attack surface and keeping your critical infrastructure safe from credential‑theft attacks.





