NewHalo Edge 3 is here: HTTP/3 everywhere and 42 edge locations.Learn more
Docs / Tools and troubleshooting

Add two-factor authentication to SSH

Tools and troubleshooting8 min readUbuntu 24.04 LTS

An SSH key is strong, but a stolen laptop or a leaked key file is still a risk. Two-factor authentication adds a second check: after your key is accepted, the server also asks for a six-digit code from an app on your phone. This guide uses a standard PAM module for time-based codes, which works with any time-based one-time password (TOTP) app.

Before you begin

  • A VPS running Ubuntu 24.04 LTS, with SSH key login already working for a user such as alex. Do not continue until key login works.
  • A TOTP app on your phone, such as any authenticator app that supports time-based codes.
  • A way back in if something goes wrong. Keep your current SSH session open, and know how to use the provider's console, as described in the rescue console guide.

Step 1: Install the PAM module

PAM is the Linux layer that handles login checks. The libpam-google-authenticator package adds a PAM module that checks six-digit codes. Install it with apt:

sudo apt update
sudo apt install -y libpam-google-authenticator

Step 2: Create your secret for your account

Run the setup command as your own user, not as root, because the secret is stored in your home folder. The tool asks a series of questions. It prints a QR code and a secret key to scan into your phone's app, plus emergency codes to keep somewhere safe.

google-authenticator
Do you want authentication tokens to be time-based (y/n) y
...
Do you want me to update your "/home/alex/.google_authenticator" file? (y/n) y

Do you want to disallow multiple uses of the same authentication
token? This restricts you to one login about every 30s, but it increases
your chances to notice or even prevent man-in-the-middle attacks (y/n) y

By default, a new token is generated every 30 seconds by the mobile app.
Do you want to allow the previous 30s window? (y/n) n

If the computer that you are logging into isn't hardware secure, then
it's popssible for an attacker to have gained access to your account...
Do you want to enable rate-limiting? (y/n) y

Answer y to time-based codes, to updating your file, to disallowing reuse, and to rate limiting. Answering n to the previous-window question keeps the codes tight. Scan the QR code in your authenticator app now, and store the emergency scratch codes in a password manager.

Careful: The file ~/.google_authenticator holds your secret. Keep it private, and never share a screenshot of the QR code or the secret.

Step 3: Tell PAM to ask for the code

Edit the SSH PAM file and add one line that requires the time-based code check. Open the file with sudo:

sudo nano /etc/pam.d/sshd

Add this line near the top, above the line that says @include common-auth:

auth required pam_google_authenticator.so

Step 4: Enable keyboard-interactive login and require both factors

The code prompt uses SSH's keyboard-interactive method, so turn it on. Then require that a user passes both a key and the code, using AuthenticationMethods. Put the settings in a drop-in file, which is easy to remove later. Files in /etc/ssh/sshd_config.d/ are read before the main config, so they take priority:

sudo nano /etc/ssh/sshd_config.d/50-two-factor.conf
KbdInteractiveAuthentication yes
AuthenticationMethods publickey,keyboard-interactive

Check the syntax, then confirm the effective setting. The sshd -T command prints the settings SSH will really use:

sudo sshd -t
sudo sshd -T | grep -Ei 'kbdinteractive|authenticationmethods'
kbdinteractiveauthentication yes
authenticationmethods publickey,keyboard-interactive

Step 5: Reload SSH and test in a second session

Reload the SSH service so the changes apply. A reload keeps your current session open, which is what you want while you test.

sudo systemctl reload ssh.service

Do not close your working session yet. From your computer, open a new terminal and connect:

ssh alex@203.0.113.10
Authenticated with partial success.
Verification code: 

Enter the current six-digit code from your app. If you are in, you have set up two-factor correctly. Only then close the old session.

Step 6: Plan for lost phones

Write down where your emergency codes are stored. If you lose your phone, one emergency code logs you in, and you can then re-run google-authenticator to set up a new secret. Note that the emergency codes are single use.

Tip: Set up two-factor for one account, test it fully, then roll it out to others. Each user needs their own google-authenticator run.
Careful: If you enable this before your key works, or you close your only session before testing, you can lock yourself out. Keep the console route ready as a fallback.

Troubleshooting

  • "Verification code:" never appears. The PAM line is missing, or it sits below @include common-auth in /etc/pam.d/sshd. Check the placement and reload SSH.
  • The code is always rejected. Your phone's clock is probably out of sync. Turn on automatic date and time on the phone, then try the next code. Rate limiting may also be blocking you for a short while.
  • You are asked for a code but your key is never checked. Confirm the AuthenticationMethods line with sudo sshd -T. The drop-in file must exist, and must use the exact spelling shown.
  • You are locked out after a change. Use the provider's console, comment out the new lines in /etc/pam.d/sshd and the drop-in file, then run sudo sshd -t and reload SSH.

Next steps

With two factors in place, review your remaining exposure. Read the guide on checking open ports and listening services to confirm which doors remain open, and consider limiting SSH to trusted addresses with a UFW rule.

Something not working? Chat or email our support team and we will help you get unstuck.

Contact support