fail2ban on Debian 12 matches nothing by default
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.