Adding OTP (One-Time Password) 2FA to SSH logins reduces the impact of stolen passwords or keys by demanding a second factor that changes every few seconds. Requiring an app-generated code in addition to the usual password hardens remote shell access against brute-force attacks and credential reuse.

On Linux servers, OTP-based 2FA commonly relies on PAM (Pluggable Authentication Modules) and the libpam-google-authenticator module. PAM mediates the authentication flow, requesting a password first and then delegating OTP verification to pam_google_authenticator.so using a shared secret stored in each user's /home/USERNAME/.google_authenticator file.

Enabling OTP for SSH modifies both /etc/ssh/sshd_config and /etc/pam.d/sshd, which directly control remote access. Misconfiguration can block new logins, so changes should be tested in an existing session and applied gradually to specific users before rolling out globally. A compatible TOTP app (such as Google Authenticator, Authy, or FreeOTP) and accurate time synchronization between server and phone are required.

Steps to configure OTP 2FA/MFA for SSH authentication:

  1. Open a terminal session on the SSH server with sudo privileges.
  2. Install the libpam-google-authenticator module on the server.
    $ sudo apt update && sudo apt install --assume-yes libpam-google-authenticator
    Reading package lists... Done
    ##### snipped #####
  3. Open the sshd configuration file in a text editor.
    $ sudo vi /etc/ssh/sshd_config

    Incorrect changes to /etc/ssh/sshd_config can block new SSH sessions; keep an existing privileged session open while editing.

  4. Set the ChallengeResponseAuthentication directive to yes.
    ChallengeResponseAuthentication yes

    Remove a leading # to uncomment the directive if necessary.

  5. Configure a Match User block for the account that must use OTP 2FA if applying the change to a specific user instead of globally.
    Match User username
        AuthenticationMethods keyboard-interactive

    Replace username with the account name that should use OTP 2FA, or omit the Match User block to rely on the default keyboard-interactive method for all users.

  6. Save and exit the editor after completing the sshd configuration changes.
  7. Validate the sshd configuration syntax before restarting the service.
    $ sudo sshd -t

    Resolve any reported errors before restarting; starting sshd with an invalid configuration can terminate remote access.

  8. Restart the SSH service to apply the new configuration.
    $ sudo systemctl restart ssh
  9. Open the PAM configuration file for sshd in a text editor.
    $ sudo vi /etc/pam.d/sshd
  10. Append entries to require the account password first and then the Google Authenticator OTP.
    auth    required      pam_unix.so     no_warn try_first_pass
    auth    required      pam_google_authenticator.so

    The first line authenticates the UNIX password, and the second line invokes pam_google_authenticator.so for OTP verification.

  11. Save and exit the editor after updating the PAM configuration.
  12. Run the google-authenticator setup command for the current user.
    $ google-authenticator

    Run this initialization separately for each account that should use OTP; each user has its own shared secret and scratch codes.

  13. Respond with y and press [ENTER] to choose time-based authentication tokens.
    Do you want authentication tokens to be time-based (y/n) y
  14. Scan the generated QR code using a compatible TOTP application such as Google Authenticator, Authy, or FreeOTP on a trusted mobile device.

    Scanning the QR code on an untrusted device exposes the shared secret and allows OTP generation by third parties.

  15. Enter the verification code displayed in the app and press [ENTER] to confirm pairing and view the emergency scratch codes.
    Enter code from app (-1 to skip): 466822
    Code confirmed
    Your emergency scratch codes are:
      21277551
      15985104
      82336172
      67376642
      85166830

    Store scratch codes offline in a secure location to recover access if the mobile device is unavailable.

  16. Respond with y and press [ENTER] to write the configuration to the user's /.google_authenticator file.
    Do you want me to update your "/home/user/.google_authenticator" file? (y/n) y
  17. Respond with y and press [ENTER] to restrict each time-based token to a single login attempt.
    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

    Answering n keeps tokens usable multiple times within the validity window but slightly reduces protection against replay attacks.

  18. Respond with y and press [ENTER] to adjust for potential time skew between the server and mobile device if required.
    By default, a new token is generated every 30 seconds by the mobile app.
    In order to compensate for possible time-skew between the client and the server,
    we allow an extra token before and after the current time. This allows for a
    time skew of up to 30 seconds between authentication server and client. If you
    experience problems with poor time synchronization, you can increase the window
    from its default size of 3 permitted codes (one previous code, the current
    code, the next code) to 17 permitted codes (the 8 previous codes, the current
    code, and the 8 next codes). This will permit for a time skew of up to 4 minutes
    between client and server.
    Do you want to do so? (y/n) y
  19. Respond with y and press [ENTER] to enable rate limiting for OTP attempts.
    If the computer that you are logging into isn't hardened against brute-force
    login attempts, you can enable rate-limiting for the authentication module.
    By default, this limits attackers to no more than 3 login attempts every 30s.
    Do you want to enable rate-limiting? (y/n) y

    Rate limiting slows automated guessing of OTP codes by limiting login attempts to three every 30 seconds.

  20. Open a new terminal or client window and attempt an SSH login to verify that both password and OTP are required.
    $ ssh user@server
    Password:
    Verification code:
    Welcome to Ubuntu 22.04 LTS
    ##### snipped #####
Discuss the article:

Comment anonymously. Login not required.