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.