In this tutorial, you will set up SSH multi-factor authentication on Ubuntu 24.04 so an interactive administrator must present both a valid SSH key and a time-based one-time password (TOTP) code. The procedure uses Ubuntu's libpam-google-authenticator package, OpenSSH keyboard-interactive authentication, explicit rollback files, and a second-session test before the original SSH connection is closed.
SSH MFA is an authentication design that requires more than one independent factor before a login succeeds. In this setup, the SSH private key is the first factor and a rotating TOTP code from an authenticator app is the second factor.
Raff Technologies supports 3,000+ customers and 15,000+ VMs. A Raff Linux VM provides the Ubuntu environment, root-level SSH configuration, firewall controls, snapshots, backups, and web console access needed to operate administrative access safely.
The original SSH MFA workflow was tested on a Raff Ubuntu 24.04 VM with 1 vCPU and 2 GB RAM. This revision corrects the PAM flow for SSH key + TOTP specifically and refreshes the configuration against Ubuntu 24.04's current OpenSSH 9.6p1 and libpam-google-authenticator documentation. Ubuntu 24.04 currently ships libpam-google-authenticator version 20191231-2build1.
Prerequisites:
- A Raff Ubuntu 24.04 VM
- A non-root administrator account with sudo privileges
- Working SSH key authentication for every interactive administrator who will use this server
- A TOTP-compatible authenticator app
- Access to the Raff web console or another recovery path in case SSH configuration must be rolled back
Scope: This tutorial configures SSH key + TOTP. It deliberately disables SSH password authentication and removes the normal PAM password check from the SSH authentication stack. If automation or service accounts depend on unattended SSH, review those accounts before applying MFA globally.
Step 1 — Verify SSH key access and create a safe rollback point
Keep your current SSH session open for the entire change. Open a second terminal and confirm that your SSH key works before modifying PAM or OpenSSH:
ssh your-user@your-server-ip
On the server, confirm the current user and active SSH service:
whoami systemctl is-active ssh
Back up the two configuration files that will change:
sudo cp --preserve=all /etc/pam.d/sshd /etc/pam.d/sshd.pre-mfa sudo cp --preserve=all /etc/ssh/sshd_config /etc/ssh/sshd_config.pre-mfa
Create a backup directory for any SSH drop-ins you add during this tutorial:
sudo install -d -m 700 /root/ssh-mfa-backup sudo cp -a /etc/ssh/sshd_config.d /root/ssh-mfa-backup/sshd_config.d
Verify the backups exist:
sudo ls -l /etc/pam.d/sshd.pre-mfa /etc/ssh/sshd_config.pre-mfa sudo test -d /root/ssh-mfa-backup/sshd_config.d && echo "SSH drop-in backup exists"
Verification is complete when SSH key login works in a second session and the PAM, main SSH configuration, and drop-in directory have backup copies.
Step 2 — Install the Google Authenticator PAM module
Update APT metadata and install the Ubuntu 24.04 package:
sudo apt update sudo apt install -y libpam-google-authenticator
Check the installed package version:
dpkg-query -W -f='${Package} ${Version}\n' libpam-google-authenticator
Ubuntu 24.04 currently provides 20191231-2build1. The package includes the google-authenticator enrollment command and pam_google_authenticator.so, which validates TOTP codes during PAM authentication.
Verify the PAM module exists:
test -f /usr/lib/x86_64-linux-gnu/security/pam_google_authenticator.so \ || find /usr/lib -name pam_google_authenticator.so -print
For package details, see the Ubuntu 24.04 package record.
Verification is complete when the package is installed and pam_google_authenticator.so is present on the server.
Step 3 — Enroll the SSH administrator in TOTP
Run the enrollment command as the same non-root user who will log in over SSH:
google-authenticator -t -d -r 3 -R 30 -w 3 -e 5
The options configure:
- TOTP rather than counter-based HOTP
- Reuse protection for a code that has already authenticated
- A maximum of 3 attempts every 30 seconds
- The normal three-code time window: previous, current, and next 30-second step
- Five emergency codes
Scan the QR code with your authenticator app and verify the first code when prompted. Store the emergency codes somewhere protected and separate from the VM.
The command writes the user's secret and settings to:
~/.google_authenticator
Set the permissions explicitly to the mode expected by the Ubuntu PAM module:
chmod 600 ~/.google_authenticator
Verify ownership and permissions without displaying the secret:
stat -c '%U %G %a %n' ~/.google_authenticator
Expected state resembles:
your-user your-user 600 /home/your-user/.google_authenticator
Check server time synchronization because TOTP depends on reasonably accurate time:
timedatectl show -p NTPSynchronized --value
Expected output:
yes
The Ubuntu PAM module requires a per-user secret file unless the weaker nullok option is used. This tutorial does not use nullok; enroll every interactive SSH administrator before enforcing MFA. See the Ubuntu PAM module documentation for the secret-file and permission rules.
Verification is complete when the authenticator app generates matching TOTP codes, ~/.google_authenticator is owned by the SSH user with mode 600, and the server clock is synchronized.
Step 4 — Enroll every other interactive SSH administrator before enforcement
If this server has more than one human administrator, each account must run its own enrollment before you enable the global MFA requirement:
google-authenticator -t -d -r 3 -R 30 -w 3 -e 5 chmod 600 ~/.google_authenticator
For each expected administrator, verify the secret file exists without reading it:
sudo stat -c '%U %a %n' /home/USERNAME/.google_authenticator
Do not share one TOTP secret between multiple people. Each administrator should have an independent secret and recovery codes.
If a non-human service account uses SSH keys for automation, do not continue with a global MFA policy until you have designed and tested an explicit exception or a different access path for that service account.
Verification is complete when every human SSH administrator who must retain access has an individual .google_authenticator file and any unattended SSH accounts have been identified before enforcement.
Step 5 — Configure PAM for SSH key plus TOTP
Ubuntu's default /etc/pam.d/sshd includes the common password-authentication stack through:
@include common-auth
For SSH key + TOTP, leaving that line active can cause PAM keyboard-interactive authentication to request a Unix password in addition to the TOTP code. This tutorial removes that password check from the SSH auth stack and uses pam_google_authenticator.so as the keyboard-interactive factor.
Edit the SSH PAM file:
sudoedit /etc/pam.d/sshd
Near the top, comment the normal password-auth line and add the Google Authenticator module:
# Standard Un*x authentication. # @include common-auth auth required pam_google_authenticator.so
Leave the existing account, session, and password-update sections unchanged.
Verify the relevant lines:
sudo grep -nE 'common-auth|pam_google_authenticator' /etc/pam.d/sshd
Expected state:
# @include common-auth auth required pam_google_authenticator.so
Do not add nullok. That option permits accounts without a secret file to bypass the TOTP module during rollout, which conflicts with the mandatory-MFA outcome of this tutorial.
Verification is complete when common-auth is commented only in /etc/pam.d/sshd and pam_google_authenticator.so is an active required auth module.
Step 6 — Configure OpenSSH to require the key and PAM TOTP challenge
Ubuntu 24.04's OpenSSH configuration includes /etc/ssh/sshd_config.d/*.conf before the main settings, and OpenSSH uses the first value it obtains for most keywords. Create an early drop-in so the MFA settings win over later defaults and cloud-init files:
sudo tee /etc/ssh/sshd_config.d/00-raff-ssh-mfa.conf > /dev/null <<'EOF' PubkeyAuthentication yes PasswordAuthentication no KbdInteractiveAuthentication yes UsePAM yes AuthenticationMethods publickey,keyboard-interactive:pam PermitRootLogin no EOF
This requires both methods in sequence:
publickey → keyboard-interactive via PAM
keyboard-interactive:pam explicitly selects PAM as the second authentication device. PasswordAuthentication no prevents OpenSSH's separate password method from becoming an alternate login path.
ChallengeResponseAuthentication is a deprecated alias for KbdInteractiveAuthentication; use the modern directive name on Ubuntu 24.04. The current Ubuntu sshd_config documentation also confirms that AuthenticationMethods can require multiple comma-separated methods in sequence. See the Ubuntu 24.04 OpenSSH configuration manual.
Validate syntax before touching the running daemon:
sudo sshd -t
No output means the syntax is valid.
Inspect the effective authentication settings:
sudo sshd -T | grep -E '^(pubkeyauthentication|passwordauthentication|kbdinteractiveauthentication|usepam|authenticationmethods|permitrootlogin) '
Expected values include:
pubkeyauthentication yes passwordauthentication no kbdinteractiveauthentication yes usepam yes authenticationmethods publickey,keyboard-interactive:pam permitrootlogin no
Verification is complete when sshd -t returns no error and sshd -T shows the intended key-plus-PAM authentication policy.
Step 7 — Reload SSH without closing the recovery session
Keep the original SSH session open. Reload OpenSSH rather than stopping it:
sudo systemctl reload ssh
Verify the service remains active:
systemctl is-active ssh
Expected output:
active
Re-run the configuration checks after reload:
sudo sshd -t sudo sshd -T | grep -E '^(passwordauthentication|kbdinteractiveauthentication|authenticationmethods) '
If any check fails, restore the backups from Step 1 from the still-open recovery session before attempting another login.
Verification is complete when SSH remains active after reload and the effective settings still require publickey,keyboard-interactive:pam.
Step 8 — Test SSH key plus TOTP in a new session
From a separate terminal, connect with verbose authentication output:
ssh -v your-user@your-server-ip
The expected flow is:
- The SSH public key is accepted.
- OpenSSH reports partial authentication success or moves to keyboard-interactive.
- PAM prompts for a verification code.
- You enter the current TOTP code.
- The shell opens without a Unix password prompt.
After login, confirm the remote identity:
whoami hostname
In the original recovery session, inspect recent SSH authentication events:
sudo journalctl -u ssh --since '-10 minutes' --no-pager | tail -n 80
On Ubuntu systems that log SSH authentication to /var/log/auth.log, you can also inspect:
sudo tail -n 80 /var/log/auth.log
A successful test must require both the key and a valid TOTP code. A key-only login should fail, and a TOTP code without the matching private key must not be enough to authenticate.
Verification is complete when a new SSH session succeeds only after the SSH key and TOTP challenge both pass, with no Unix password prompt.
Step 9 — Verify recovery before closing the original session
Test one emergency code from a controlled second session only if your recovery policy permits consuming one. Each emergency code is single-use.
At minimum, verify that the protected recovery material exists and that you can reach an out-of-band recovery path such as the Raff web console before closing the original connection.
Keep these rollback commands available in the original session:
sudo cp --preserve=all /etc/pam.d/sshd.pre-mfa /etc/pam.d/sshd sudo cp --preserve=all /etc/ssh/sshd_config.pre-mfa /etc/ssh/sshd_config sudo rm -f /etc/ssh/sshd_config.d/00-raff-ssh-mfa.conf sudo rm -rf /etc/ssh/sshd_config.d sudo cp -a /root/ssh-mfa-backup/sshd_config.d /etc/ssh/sshd_config.d sudo sshd -t sudo systemctl reload ssh
Use this rollback only from an already authenticated console or SSH session. Do not close the working session until a separate MFA login has succeeded.
Verification is complete when a separate MFA login works and you have confirmed a usable recovery path before ending the original session.
Step 10 — Verify SSH MFA end to end
Run the final server checks:
sudo sshd -t sudo sshd -T | grep -E '^(pubkeyauthentication|passwordauthentication|kbdinteractiveauthentication|usepam|authenticationmethods|permitrootlogin) ' stat -c '%U %a %n' ~/.google_authenticator systemctl is-active ssh
From a new client session, verify the full login path again:
ssh your-user@your-server-ip
End-to-end verification is complete when all of the following are true:
- SSH key authentication succeeds as the first factor
- A TOTP prompt is required as the second factor
- No Unix password prompt appears
PasswordAuthenticationis disabled- Root SSH login is disabled
pam_google_authenticator.sois required in/etc/pam.d/sshd~/.google_authenticatoris owned by the user with mode600- A second MFA-protected SSH session opens successfully
- The original recovery session is not closed until the second session works
