Two-factor authentication (2FA) adds a second layer of security to SSH logins by requiring both something you know (a password or private key) and something you have (a time-based one-time password generated by an authenticator app). On RHEL 8, the Google Authenticator PAM module is available through EPEL and integrates with OpenSSH via PAM and a small change to sshd_config. This tutorial shows you how to install and configure Google Authenticator, update the PAM stack, restrict SSH to require both a public key and an OTP, and test the setup safely without locking yourself out.
Prerequisites
- RHEL 8 server with OpenSSH running and an existing SSH key-pair login
- EPEL 8 repository enabled (
dnf install -y epel-release) - A smartphone with an authenticator app installed (Google Authenticator, Aegis, or Authy)
- Two open SSH sessions to the server during configuration — one to act as a safety net
Step 1 — Install the Google Authenticator PAM Module
Install the PAM module and its dependencies from EPEL.
sudo dnf install -y epel-release
sudo dnf install -y google-authenticator
Verify the PAM library was installed to the correct path.
ls /usr/lib64/security/pam_google_authenticator.so
Step 2 — Initialise Google Authenticator for Your User Account
Run the setup utility as the user who will authenticate (not as root unless root SSH login is required). Answer the interactive prompts to generate a TOTP secret and QR code.
google-authenticator
Respond to the prompts as follows for the most secure and convenient configuration:
- Do you want authentication tokens to be time-based? —
y(TOTP, not HOTP) - Scan the QR code printed in the terminal with your authenticator app, or manually enter the secret key shown
- Update your ~/.google_authenticator file? —
y - Disallow reuse of the same token? —
y - Permit a time skew of up to 4 minutes? —
y(recommended for servers with minor clock drift) - Enable rate-limiting? —
y
Record the five emergency scratch codes printed during setup and store them securely offline. They are single-use backup codes for account recovery when your phone is unavailable.
Step 3 — Configure PAM for SSH
Edit the SSH PAM configuration to add the Google Authenticator module. Open the file in your editor.
sudo vi /etc/pam.d/sshd
Add the following line at the very top of the file, before any other auth lines:
auth required pam_google_authenticator.so
The complete top of /etc/pam.d/sshd should look like this after the change:
auth required pam_google_authenticator.so
auth substack password-auth
auth include postlogin
Step 4 — Configure sshd to Use PAM Challenge-Response
Edit the OpenSSH server configuration to enable challenge-response authentication and specify the required authentication methods.
sudo vi /etc/ssh/sshd_config
Locate and set the following directives (add them if they do not exist):
ChallengeResponseAuthentication yes
AuthenticationMethods publickey,keyboard-interactive
UsePAM yes
This configuration requires SSH clients to first authenticate with a public key and then complete a keyboard-interactive challenge (the OTP prompt). Reload SSH to apply the changes without dropping existing sessions.
sudo systemctl reload sshd
Step 5 — Test the MFA Login
Open a new terminal (keeping your existing session open as a fallback) and connect to the server.
ssh user@your-server-ip
After public key authentication succeeds, you will be prompted for a Verification Code. Open your authenticator app, find the entry for this server, and type the current six-digit code. If the code is accepted, login completes successfully. If you are locked out, use your safety-net session to revert the PAM or sshd changes.
# Verify PAM and sshd are configured correctly if login fails
sudo sshd -t
sudo journalctl -u sshd -n 50
Step 6 — Enforce 2FA for All Users System-Wide
By default, only users who have run google-authenticator and have a ~/.google_authenticator file are challenged for an OTP. To require all users to have 2FA configured before they can log in, add the nullok option removal and set a strict policy.
# In /etc/pam.d/sshd, the line WITHOUT nullok enforces 2FA for all users:
auth required pam_google_authenticator.so
# To allow users without ~/.google_authenticator to still log in during rollout:
auth required pam_google_authenticator.so nullok
Remove nullok once every user account has been initialised to enforce strict 2FA organisation-wide.
Conclusion
You have installed the Google Authenticator PAM module on RHEL 8 from EPEL, generated a TOTP secret with emergency scratch codes, updated the PAM stack and SSH daemon configuration to require both a public key and a one-time password, and verified the login flow. This layered approach significantly reduces the risk of account compromise from stolen private keys.
Next steps: Harden SSH with AllowUsers and TCP Wrappers on RHEL 8, Configure Fail2Ban to Block Brute-Force SSH Attempts on RHEL 8, and Audit Linux Security with Lynis on RHEL 8.