With an SSH key, an attacker has to steal a file from your computer. With two-factor authentication, they also need your phone. This guide adds a six-digit code, renewed every 30 seconds, to every SSH login, on top of the key.
1. How it works
The TOTP standard (Time-based One-Time Password) generates codes from a shared secret and the current time. Any compatible app works: Google Authenticator, Microsoft Authenticator, Aegis, 2FAS, or the TOTP feature of a password manager. On the server side, the PAM module libpam-google-authenticator checks the codes, whatever app you use.
The result: logging in requires the SSH key, then the code. No password is asked.
2. Before you start
- Key-based login must already work, with passwords disabled: see securing SSH.
- The server clock must be accurate, since codes depend on the time. Check with
timedatectl: the lineSystem clock synchronizedmust sayyes. - Keep a root session open throughout the procedure.
3. Install the module
apt install -y libpam-google-authenticator
4. Enrol your phone
Run the command as the account that will log in, not as root:
su - alex
google-authenticator -t -d -f -r 3 -R 30 -w 3
These options answer the tool's questions in advance: time-based codes (-t), each code usable only once (-d), at most three attempts every 30 seconds (-r 3 -R 30), and a tolerance of one code before and after to absorb a slight clock drift (-w 3).
A QR code appears in the terminal: scan it with your app. If the terminal is too small, enlarge the window or type in the secret key shown just below it.
5. Configure PAM
Open /etc/pam.d/sshd. Comment out the line that asks for the account password and add the TOTP module instead:
# @include common-auth
auth required pam_google_authenticator.so nullok
Commenting out common-auth is what prevents a password from being asked on top of the code. The nullok option lets accounts that have not enrolled a phone yet log in with the key alone: handy during setup. Remove it once every account is enrolled.
6. Configure OpenSSH
If you followed our hardening guide, open /etc/ssh/sshd_config.d/00-durcissement.conf and edit the existing line rather than adding one in another file: OpenSSH only keeps the first value it reads.
KbdInteractiveAuthentication yes
UsePAM yes
AuthenticationMethods publickey,keyboard-interactive
AuthenticationMethods publickey,keyboard-interactive requires both steps, in this order: the key first, then the code. Leave PasswordAuthentication no in place.
sshd -t && systemctl reload ssh
sshd -T | grep -E "kbdinteractive|authenticationmethods|usepam"
7. Test without locking yourself out
From a second terminal, keeping the current session open:
ssh alex@SERVER_IP
The key is checked, then the server displays Verification code:. Enter the code from the app. If the login fails:
- The code is rejected: check the time on the server and on the phone.
- A password is asked: the
@include common-authline is not commented out. - No code is asked:
KbdInteractiveAuthenticationis still set tono; check withsshd -T.
Details of the failure appear in journalctl -u ssh -n 30.
8. Backup codes and exceptions
Each backup code works once, in place of the app's code. If you are running low, run google-authenticator again to generate a new secret and new codes; you will then need to scan the new QR code.
Automated scripts, such as a backup that connects over SSH, cannot type a code. Exempt one specific technical account at the end of /etc/ssh/sshd_config:
Match User borg
AuthenticationMethods publickey
Restrict that account to the bare minimum, as described in backups with Borg. And if you lose both your phone and your backup codes, the rescue console (VNC) in the client area, or failing that our support team, is how you get back in.
Frequently asked questions
Is SSH two-factor authentication useful if I already use a key?
It protects you against your key being stolen: a compromised computer, an unencrypted backup or a key copied by mistake. For a server that holds customer data or payments, it is a reasonable safeguard. For a test server, a well-protected key is usually enough.
What if I lose my phone?
Use one of the backup codes shown during enrolment, then run google-authenticator again to enrol a new phone. Without backup codes, use the rescue console in the client area or contact support.
Do I have to use the Google Authenticator app?
No. The server module has that name, but it follows the TOTP standard: any compatible app works, including Aegis, 2FAS, Microsoft Authenticator or the TOTP feature of a password manager.
Why is my code rejected when it is correct?
Almost always because of a clock drift between the server and the phone. Check that the server is synchronised with timedatectl, and that the phone's time is set automatically.










