Hardening SSH
Why SSH Still Matters (and Why It’s Still Attacked)
SSH is the backbone of modern infrastructure administration. Whether you manage cloud workloads, on‑prem servers, Kubernetes nodes, or embedded systems, SSH is still the control plane.
And that’s exactly why it’s relentlessly attacked.
Credential stuffing
Leaked private keys
Reused passwords
Forgotten jump hosts
Even with key‑based authentication, a single compromised workstation can become a disaster.
Defense in depth matters. One of the simplest and most effective layers you can add is TOTP‑based multi‑factor authentication using PAM.
This post walks through why and how to add TOTP to SSH without breaking automation or locking yourself out.
What We’re Building
We’ll implement:
SSH authentication using:
Public key authentication
TOTP (Time‑based One‑Time Password)
Enforced via PAM (Pluggable Authentication Modules)
Compatible with:
Google Authenticator
FreeOTP
Authy (manual entry)
Bitwarden
Many more….
This setup works on most Linux distributions (Debian, Ubuntu, RHEL, Rocky, Alma).
Threat Model (Quick Reality Check)
This protects against:
Stolen SSH private keys
Password reuse
Brute‑force attacks
Lateral movement from compromised hosts
This does not protect against:
Root compromise
Malicious PAM modules
A fully compromised client and unlocked authenticator
Security is layers, not magic.
Step 1 – Install the PAM TOTP Module
On Debian / Ubuntu:
apt install libpam-google-authenticator
On RHEL‑based systems:
dnf install google-authenticator
Yes, the package name is confusing. No, it does not require Google services.
Step 2 – Enroll a User
Run the enrolment as the target user, not root:
google-authenticator
Recommended answers:
Time‑based tokens: yes
Update ~/.google_authenticator: yes
Disallow multiple uses: yes
Increase window: no (unless clock drift is an issue)
Enable rate‑limiting: yes
Scan the QR code with your authenticator app and store the emergency scratch codes offline.
Step 3 – Configure PAM for SSH
Edit:
/etc/pam.d/sshd
Add at the top:
auth required pam_google_authenticator.so nullok
auth required pam_permit.so
Important Notes
nullokallows users without TOTP configured to log inThis is useful for:
Service accounts
Gradual rollouts
You can remove nullok later to enforce TOTP everywhere.
Step 4 – Configure SSHD
Edit:
/etc/ssh/sshd_config
Ensure the following settings:
UsePAM yes
ChallengeResponseAuthentication yes
KbdInteractiveAuthentication yes
AuthenticationMethods publickey,keyboard-interactive
Then reload SSH:
systemctl reload sshd
⚠️ Do not close your existing SSH session yet.
Step 5 – Test Safely
Open a new SSH session and connect.
Expected flow:
SSH key authentication
TOTP prompt
Successful login
If it fails:
Check logs:
journalctl -u sshd -f
Verify file permissions:
chmod 600 ~/.google_authenticator
Automation & Service Accounts (The Right Way)
You should not disable MFA globally just to keep automation working.
Instead:
Option 1 – Dedicated Accounts
No TOTP enrollment
nullokenabledRestricted via:
AllowUsersMatch UserblocksForced commands
Option 2 – Match Blocks
Match User deploy
AuthenticationMethods publickey
This keeps humans protected while automation stays predictable.
Common Pitfalls
❌ Forgetting
UsePAM yes❌ Testing in the same SSH session
❌ Locking out root without console access
❌ Enforcing MFA on service accounts
Security should slow attackers, not engineers.
Final Thoughts
TOTP via PAM is one of the highest ROI security improvements you can make to SSH:
Minimal complexity
No external dependencies
No cloud lock‑in
Strong protection against real‑world attacks
If SSH is still your control plane — and it is — it deserves MFA.
Next posts will go deeper into:
SSH certificates
Bastion hosts vs Zero Trust
Hardware‑backed SSH keys
Breaking legacy authentication safely
Security is not a product. It’s a posture.