Reported symptoms
- Alice authenticates from the office LAN with her key. The same key, the same account, from the VPN pool, is rejected.
- The client message is
Permission denied (publickey,keyboard-interactive)— after the server has already accepted the key. sshd -treported no error and the service restarted cleanly, so the change that introduced this looked safe.- Nothing about the account, the key, or
~/.sshpermissions differs between the two paths.
Evidence provided
$ ssh -vvv alice@server01.example.com
...
debug1: Offering public key: /home/alice/.ssh/id_ed25519 ED25519
debug1: Server accepts key: /home/alice/.ssh/id_ed25519 ED25519
Authenticated with partial success.
debug1: Authentications that can continue: keyboard-interactive
debug2: we did not send a packet, disable method
alice@server01.example.com: Permission denied (publickey,keyboard-interactive).
$ sudo journalctl -u ssh --since '10 minutes ago' | tail -4
Aug 11 09:14:02 server01 sshd[4411]: Postponed publickey for alice from 203.0.113.7 port 51422 ssh2 [preauth]
Aug 11 09:14:02 server01 sshd[4411]: Failed keyboard-interactive for alice from 203.0.113.7 port 51422 ssh2
Aug 11 09:14:02 server01 sshd[4411]: Connection closed by authenticating user alice 203.0.113.7 port 51422 [preauth]
$ sudo sshd -t -f /etc/ssh/sshd_config; echo "exit=$?"
exit=0
$ sudo sshd -T -C user=alice,host=vpn,addr=203.0.113.7 | grep -E 'authenticationmethods|kbdinteractive|passwordauth'
passwordauthentication no
kbdinteractiveauthentication no
authenticationmethods publickey,keyboard-interactive
$ sudo sshd -T -C user=alice,host=office,addr=192.0.2.5 | grep -E 'authenticationmethods|kbdinteractive|passwordauth'
passwordauthentication no
kbdinteractiveauthentication no
authenticationmethods publickey
$ sudo grep -n -A3 '^Match' /etc/ssh/sshd_config
41:Match Address 203.0.113.0/24
42- AuthenticationMethods publickey,keyboard-interactive
Work the evidence before reading on
Three facts have to be reconciled:
- The server accepts the key — the log says
Postponed publickey, notFailed publickey. So the key, the account and the file permissions are all fine. This is not an authorization problem. - The two
sshd -T -Cruns differ in exactly one line, and that line names a method the very same output says is disabled. sshd -tis happy. Whatever is wrong, it is not a syntax error.
Write down what “partial success” means before continuing. It is the single most informative word in the whole transcript, and it rules out most of the causes an engineer reaches for first.
Root cause
1. AuthenticationMethods is a conjunction, not a menu
AuthenticationMethods takes one or more comma-separated lists.
Successful authentication requires completing every method in at
least one list. publickey,keyboard-interactive therefore means “key
and keyboard-interactive”, not “key or keyboard-interactive”.
The key succeeds, which is why sshd reports Authenticated with partial success and asks for the next method. There is no next method available,
so the connection dies.
2. The Match block requires a method the global section disabled
The intent was MFA for remote logins. The block supplies the requirement but not the mechanism:
KbdInteractiveAuthentication no # global
Match Address 203.0.113.0/24
AuthenticationMethods publickey,keyboard-interactive
Match blocks override only the keywords they actually contain.
KbdInteractiveAuthentication is not one of them, so the global no
still applies inside the block. The result is a requirement that can
never be satisfied: sshd demands a method it will not run.
The office prefix does not match the block, keeps the global
AuthenticationMethods publickey, and works perfectly — which is exactly
why this reads as a client-side fault for the first hour of the incident.
3. Why the log looked like a user problem
Failed keyboard-interactive for alice names the user and a method the
user never chose. An operator scanning the journal reads it as “alice
failed to authenticate” and starts investigating alice. The preceding
Postponed publickey line — the one that proves the server already
accepted her — is the line that matters, and it does not contain the
word “failed” so it does not survive a grep -i fail.
Resolution
- Keep an escape hatch open. Before touching sshd, open a second session and leave it connected. A reload does not drop established sessions, so this is your way back in if the next step is also wrong
- Decide what the requirement actually is. The Match block was added to force MFA on remote logins. Either implement MFA properly, or drop the requirement — do not leave a rule that demands an unavailable method
- If MFA is intended: enable the second factor inside the same Match block and give it something to run. Add
KbdInteractiveAuthentication yesto the block, confirmUsePAM yesglobally, and configure the TOTP module in the PAM stack for sshd: - ``
Match Address 203.0.113.0/24 AuthenticationMethods publickey,keyboard-interactive KbdInteractiveAuthentication yes`` - If MFA is not intended: reduce the block to the methods that are actually enabled —
AuthenticationMethods publickey— or remove the block entirely if it now says nothing the global section does not - Verify before reloading. Render the configuration for both source networks and confirm every listed method is enabled:
- ``
sudo sshd -T -C user=alice,host=vpn.example.com,addr=203.0.113.7 | grep -E 'authenticationmethods|kbdinteractive' sudo sshd -T -C user=alice,host=office.example.com,addr=192.0.2.5 | grep -E 'authenticationmethods|kbdinteractive'`` - Reload, do not restart.
sudo systemctl reload sshre-reads the configuration without dropping established sessions. Then authenticate from the VPN in a third terminal, while both earlier sessions stay open
Verification
- Confirm the rendered configuration is self-consistent. For each source prefix, every method in
authenticationmethodsappears as enabled in the samesshd -T -Coutput - Confirm the VPN path completes.
ssh -vvvfrom the VPN shows each method completing and the session establishing, with nopartial successdead end - Confirm the office path is unchanged. The unmatched prefix still authenticates exactly as before — a Match block fix must not alter the path it does not match
- Confirm the MFA requirement is real, if that was the intent. Authenticate from the VPN with the key alone and confirm it is rejected. A requirement that no longer rejects anything is not MFA
- Close the escape-hatch sessions last, only after a fresh connection has succeeded end to end
Prevention
- Verify every
Matchchange withsshd -T -Cfor each source network the block is meant to distinguish.sshd -tcannot catch a contradiction and should never be the last check before a reload. - Check the invariant explicitly: every method named in
AuthenticationMethodsmust be enabled in the same rendered output. This is cheap enough to run in CI against a set of representative-Ctuples. - Put global settings above the first
Matchblock. Anything below it belongs to that block whether you intended it to or not. - Reload rather than restart, and always hold an established session open across the change.
- When triaging an SSH rejection, read the log line before the failure.
Postponed <method>means that method succeeded, and it moves the investigation off the user and onto the configuration.