Key-only SSH still accepts passwords if PAM is left on
Setting PasswordAuthentication no is not sufficient. With UsePAM yes and KbdInteractiveAuthentication left at default, sshd will still complete a keyboard-interactive password login.
The received wisdom is two lines in sshd_config:
PasswordAuthentication no
PermitRootLogin no
That closes the password authentication method. It does not close
keyboard-interactive, which on most distributions is wired through PAM to the same
pam_unix password check. If KbdInteractiveAuthentication is left at its default and
UsePAM yes is set, which it is out of the box on Debian and Ubuntu, then a client that asks for
keyboard-interactive can still authenticate with the account password.
Verify rather than assume. Ask the server what it will accept:
# Ask for the list of methods without attempting to log in.
ssh -o PreferredAuthentications=none -o StrictHostKeyChecking=no user@host 2>&1 | grep -i 'authentications'
# Permission denied (publickey,password,keyboard-interactive).
If keyboard-interactive appears in that list, the door is open.
The configuration that actually closes it
# /etc/ssh/sshd_config.d/10-hardening.conf
PasswordAuthentication no
KbdInteractiveAuthentication no
ChallengeResponseAuthentication no
PermitRootLogin prohibit-password
PermitEmptyPasswords no
AuthenticationMethods publickey
MaxAuthTries 3
AuthenticationMethods publickey is the belt-and-braces line: it states the complete set of
methods that may satisfy authentication, so a method re-enabled somewhere else in the include
chain still cannot be used on its own.
Two ordering details matter on modern OpenSSH. Drop-in files under sshd_config.d are read
where the Include directive sits, near the top on Debian derivatives, and first value
wins for most keywords. A setting you add at the bottom of the main file may lose to the
drop-in. Check the effective config rather than reading the file:
# Print the config sshd will actually use, after includes and matches.
sshd -T | grep -Ei 'passwordauth|kbdinteractive|permitroot|authenticationmethods'
# Validate syntax before restarting; a typo here locks you out.
sshd -t && systemctl reload ssh
Keep a second session open while you reload. reload does not drop existing connections, which
gives you a way back in if the new config rejects you.
Rate limiting is still worth having
Key-only auth makes brute force pointless, but the log noise is real and the connection churn
is not free. fail2ban with the stock sshd jail is enough:
# /etc/fail2ban/jail.d/sshd.local
[sshd]
enabled = true
backend = systemd
maxretry = 4
findtime = 10m
bantime = 1h
# Escalate repeat offenders rather than banning everyone for a week.
bantime.increment = true
bantime.factor = 4
backend = systemd matters on any host that no longer writes /var/log/auth.log; with the
default file backend the jail starts, finds nothing to read, and reports zero failures forever
while the attempts continue.