Your SSH Key Just Leaked (Now What?)
Your private key is on GitHub. Not intentionally—you committed .env at 11 PM on a Friday and didn’t notice until Monday morning. Now someone has your SSH key. Without 2FA, they have full access to your server. Full stop.
Here’s the thing: SSH keys are amazing compared to passwords. But a leaked key is a stolen kingdom. Two-factor authentication (TOTP via your phone) won’t save you from every threat—nothing does—but it will stop that attacker cold. They have the key, sure, but they don’t have your phone. And they’re probably not patient enough to wait around.
This guide walks you through adding TOTP to both SSH and sudo using PAM (Pluggable Authentication Modules). It takes about 10 minutes, won’t break your server, and once you test it properly, you’ll sleep better.
Why 2FA for SSH Matters
People often think: “I have SSH keys. I don’t need 2FA.” But consider these scenarios:
- Leaked key: Your private key gets exposed (git mistake, compromised machine, supply chain incident). An attacker has hours or days before you notice.
- Jump host compromise: You SSH from a jump host that gets pwned. An attacker logs in as you from that machine—your key is useless if they’re already inside.
- Insider threat: A contractor or former employee still has SSH access. 2FA means they need something else.
- CI/CD credential leak: Your deployment system has a key. If that key is stolen, 2FA is another lock on the door.
TOTP doesn’t solve everything, but it converts a “full compromise” into a “maybe they get locked out when they try to sudo” situation. That’s worth 10 minutes.
Install libpam-google-authenticator
Start on your server. This works on Ubuntu, Debian, RHEL, Rocky—anywhere with PAM (which is basically everywhere).
sudo apt-get updatesudo apt-get install libpam-google-authenticatorOn RHEL/Rocky:
sudo dnf install google-authenticatorGenerate Your TOTP Secret
Run google-authenticator as your user:
google-authenticatorYou’ll see prompts. Here’s what they mean:
Do you want authentication tokens to be time-based (y/n) yAnswer yes. This gives you TOTP (time-based, what Google Authenticator and Authy use).
Next, you’ll see a QR code. Scan it with your phone (Authy, Google Authenticator, Microsoft Authenticator—any TOTP app works).
Do you want me to update your "/home/kingpin/.google_authenticator" file? (y/n) yAnswer yes. This saves your secret locally.
Do you want to disallow multiple uses of the same authentication token? (y/n) yAnswer yes. Prevents replay attacks (someone using the same code twice).
Do you want to enable rate-limiting? (y/n) yAnswer yes. Stops brute-force attacks on the OTP codes.
After this, you’ll see backup codes. Screenshot or write these down and store them somewhere safe (encrypted password manager, not your Notes app). If you lose your phone, these get you back in.
Configure sshd for TOTP
Edit /etc/ssh/sshd_config:
sudo nano /etc/ssh/sshd_configMake these changes:
#ChallengeResponseAuthentication noChallengeResponseAuthentication yes
#AuthenticationMethods publickey passwordAuthenticationMethods publickey,keyboard-interactiveThe first change enables interactive prompts. The second says: “require both a valid public key and keyboard-interactive auth (TOTP).” You must have the key and the code.
Check syntax:
sudo sshd -tIf you see no output, you’re good. If there’s an error, fix it before reloading.
Configure PAM for SSH
Edit /etc/pam.d/sshd:
sudo nano /etc/pam.d/sshdFind the line that says @include common-auth and comment it out:
@include common-auth# @include common-authThen add these lines right after (before @include common-account):
auth required pam_google_authenticator.so nullok sequential=denyThe nullok flag means: “if the user hasn’t set up TOTP yet, let them in anyway.” This prevents locking out other users. The sequential=deny flag prevents code reuse.
The Critical Test (Read This)
Do NOT close your current SSH session yet. Open a second terminal and SSH in as a test:
ssh user@your.server.comYou’ll be prompted for a password (which is your TOTP code—just the 6 digits, no special symbols). Enter the code from your phone. If you get in, great. If not, you have another terminal open to fix it.
This is the difference between a smooth deployment and being locked out of your own server at 2 AM.
Add TOTP to sudo
Now let’s require TOTP for sudo commands too. Edit /etc/pam.d/sudo:
sudo nano /etc/pam.d/sudoAdd this line right after the first @include common-auth line:
auth required pam_google_authenticator.so nullokTest it in your second terminal:
sudo -iYou’ll be prompted for a 6-digit code. Enter it. If it works, you’re good.
Fine-Tuning (Optional)
You might want TOTP for SSH login but not for key-only connections from trusted IPs (like your home network). This is possible but adds complexity—you’d modify sshd_config with Match blocks and conditional AuthenticationMethods. For now, require it everywhere. You can refine later.
Backup Codes: Don’t Lose Them
Those backup codes from google-authenticator? Print them. Store them in a safe place (encrypted password manager, physical safe). If your phone gets lost or stolen, those codes are your only way back in. Seriously.
You’re Done
You just added a second factor to SSH and sudo. An attacker with your private key now has to also have your phone. They probably don’t have your phone. That’s the whole idea.
Your future self (especially the 2 AM version) will thank you.