7 July 2026

Linux SSH 2FA with pam_radius — phone approval for SSH and sudo

Phone-approval 2FA for Linux SSH, sudo and su with pam_radius and the Notakey auth-proxy, plus a safe local fallback so you never get locked out.

The same Notakey auth-proxy that already protects your VPN and your MikroTik admin login can protect Linux logins too. Linux’s PAM stack ships a standard RADIUS module: point it at the proxy, and SSH, sudo and su all require a phone approval on top of the password.

sshd / sudo ──PAM──▶ pam_radius ──RADIUS──▶ Notakey auth-proxy ──▶ your RADIUS server

                                                    └──▶ push ──▶ user's phone

This guide is for Debian/Ubuntu (libpam-radius-auth); on RHEL-family distros the module is packaged as pam_radius (EPEL) and the PAM files live under the same /etc/pam.d/ layout.

Requirements

  • A working auth-proxy in front of a RADIUS server, set up exactly as in steps 1–3 of the VPN guide.
  • The same username in the downstream RADIUS server, in the Notakey service (with an onboarded phone), and as an account on the Linux box. PAM only authenticates; the user still needs a local (or directory) account to log into.
  • The Linux box able to reach the proxy on UDP 1812. The password travels as PAP between them, so keep this on a management network, or run the dockerized proxy on the host itself.

Step 0 — Set up your emergency exit first

Before touching PAM, make sure a purely local login works: give root (or a dedicated break-glass admin) a strong local password and verify it at the console. The configuration below always falls back to local passwords, so this account is your way in if the proxy, the RADIUS server or the network is ever down. Do this first, not after something breaks.

Step 1 — Install the module

sudo apt-get install libpam-radius-auth

Step 2 — Point it at the proxy

Edit /etc/pam_radius_auth.conf and add one line: the proxy’s address, the shared secret (pairs with the proxy’s vpn_secret_in), and the timeout in seconds:

# server[:port]    shared_secret            timeout (s)
10.0.1.20          secret_to_radius_server  60

Then lock the file down, since it contains the secret:

sudo chmod 0600 /etc/pam_radius_auth.conf

The timeout is the same trap as everywhere else in this series. The module’s default is 3 seconds: fine for a password check, hopeless for a human approval. Set it to at least the proxy’s vpn_message_ttl (30 s); 60 gives comfortable slack.

Step 3 — Enable it in PAM

For each entry point you want protected, add one line just above the @include common-auth line. For SSH:

sudo nano /etc/pam.d/sshd
auth sufficient pam_radius_auth.so
@include common-auth

sufficient means: if RADIUS answers Access-Accept (password verified downstream and push approved), the login succeeds immediately. If not, PAM falls through to the normal local password check below it.

Add the same line to any of these, as desired:

  • /etc/pam.d/login for console logins
  • /etc/pam.d/sudo — every sudo asks the phone (strong, but chatty; sudo’s credential caching keeps it tolerable)
  • /etc/pam.d/su for switching users

Finally, edit /etc/pam.d/common-auth and add try_first_pass to the existing pam_unix.so line:

auth    [success=1 default=ignore]      pam_unix.so nullok_secure try_first_pass

(The other options on that line vary by release; only try_first_pass is the addition.) Without it, a user falling back to local authentication gets prompted for the password a second time; with it, the password already typed is reused silently.

Step 4 — Check sshd is actually using PAM

In /etc/ssh/sshd_config, PAM-based authentication needs:

UsePAM yes
KbdInteractiveAuthentication yes

(both are the Debian/Ubuntu defaults). One thing to know: SSH public-key logins skip PAM’s auth phase entirely: a key-based login will not trigger a push. If you want key plus push, set AuthenticationMethods publickey,keyboard-interactive, but test that variant separately before rolling it out.

Step 5 — Create push-only users

Here is the trick that makes the setup clean: create users without a local password.

sudo useradd -m -G sudo john

No passwd afterwards: the account’s local password stays locked. That user now has exactly one way in: RADIUS through the proxy, meaning password and phone approval. There is nothing local to brute-force. Your break-glass admin from step 0 remains the only account with a purely local password.

Test it, carefully

Keep your working session open and SSH in from a second terminal with a RADIUS username:

  • The password prompt behaves as usual; after you enter it, the session waits: that’s the proxy holding the exchange open.
  • The push lands on the phone. Approve → shell. Deny or ignore → the login fails after the timeout.
  • Confirm the break-glass account still gets in with its local password.

Only close your original session after both checks pass.

Troubleshooting

  • Login fails instantly, no push. The timeout in /etc/pam_radius_auth.conf is still at its 3-second default, or the secret doesn’t match the proxy’s vpn_secret_in.
  • No push, but the wait happens. Username mismatch between RADIUS and the Notakey service, or the phone isn’t onboarded to this application.
  • Asked for the password twice. try_first_pass is missing from the pam_unix line in common-auth.
  • Key-based SSH logins get no push. That’s expected; public-key auth bypasses PAM’s auth phase (see step 4).

Where this fits

With one proxy and one RADIUS server, the VPN, the router’s admin plane and your Linux fleet all now require a phone approval. Anything that speaks RADIUS or PAM can join the same setup.

← All posts

See your first passwordless login this week

A 30-minute call with an engineer, not a sales deck. We’ll map your VPN, SSO or Windows setup to a working pilot.