fhxd.de

fail2ban on Debian 12 matches nothing by default

2026-06-02

You install fail2ban on a fresh bookworm box. systemctl is-active fail2ban says active. fail2ban-client status lists the sshd jail. Everything looks correct, and the jail will never ban anybody.

Two separate problems stack on top of each other, and neither produces an error.

Problem one: there is no auth.log

Debian 12 no longer installs rsyslog as part of a standard system. Logs live in the journal and nowhere else. The classic fail2ban filter path, /var/log/auth.log, simply does not exist.

With backend = auto fail2ban is supposed to notice this and fall back to the systemd backend. In practice, depending on how the jail inherits its settings, you can end up with a jail pointed at a file that isn't there. Set it explicitly and stop guessing:

[DEFAULT]
backend = systemd

Problem two: the unit is not called sshd

This one is nastier, because it survives fixing the first. The stock filter.d/sshd.conf ships this journal match:

journalmatch = _SYSTEMD_UNIT=sshd.service + _COMM=sshd

On Debian the OpenSSH server unit is ssh.service. sshd.service exists only as an alias. Aliases are resolved at unit lookup time; they are not what journald stamps on the record. Every log line gets _SYSTEMD_UNIT=ssh.service, and the filter's match expression selects an empty set.

You can see it in one command:

journalctl _SYSTEMD_UNIT=sshd.service _COMM=sshd --since '30 days ago' | wc -l
journalctl _SYSTEMD_UNIT=ssh.service  _COMM=sshd --since '30 days ago' | wc -l

On a box that has been on the public internet for a while, the first number is 0 or 1 and the second is in the hundreds. Override it in jail.local:

[sshd]
enabled = true
port    = 45279
mode    = aggressive
journalmatch = _SYSTEMD_UNIT=ssh.service + _COMM=sshd

Verify against real log data

Do not trust Total failed: 0 — that is also what a working jail shows on a quiet day. Replay the filter over your actual journal instead:

fail2ban-regex systemd-journal /etc/fail2ban/filter.d/sshd.conf \
  --journalmatch "_SYSTEMD_UNIT=ssh.service + _COMM=sshd"

A working setup reports a four-digit Failregex total against a year of history. A broken one reports zero, and reports it just as confidently.

A note on aggressive mode

If you have already disabled password authentication — and you should have — attackers never reach the password stage. They get refused at publickey and disconnect. The normal filter mode looks for failed passwords and sees none of this. mode = aggressive adds the preauth disconnect patterns, which is the only thing left to match on a key-only host.