Configure Fail2ban for SSH Protection on Debian 12

Install and configure Fail2ban for SSH on Debian 12: fix the missing auth.log with the systemd backend, write a jail.local, ban with nftables, test and unban.

Fail2ban is an intrusion prevention software framework written in the Python programming language that is designed to protect against brute-force attacks. It is able to run on POSIX systems that have an interface to a packet-control system or firewall installed locally, such as nftables or iptables.

Fail2ban works together with a packet-control system or firewall installed on your server, and is commonly used to block connection attempts after a number of failed tries.

It operates by monitoring log files (or the systemd journal) for certain types of entries and runs predetermined actions based on its findings.

This guide was tested on Debian 12 (bookworm) with the Debian fail2ban 1.0.2 package. The output shown is from that test, with a second machine playing the attacker.

Related content:

The Debian 12 gotcha: there is no /var/log/auth.log

Debian 12 no longer installs rsyslog by default. SSH logins go to the systemd journal only, so /var/log/auth.log does not exist. Fail2ban’s stock sshd jail still looks for that file, and on a fresh install the service fails to start:

ERROR   Failed during configuration: Have not found any log file for sshd jail
ERROR   Async configuration of server failed
fail2ban.service: Main process exited, code=exited, status=255/EXCEPTION

The fix, covered below, is to tell the jail to read the journal with backend = systemd. That backend needs the python3-systemd module. Without it the error changes to:

ERROR   Failed to initialize any backend for Jail 'sshd'

The second gotcha is the firewall: fail2ban’s default ban action is iptables-multiport, but Debian 12 uses nftables and does not install the iptables command by default. Use the nftables actions instead.

Ensure that your system is up to date

Ensure that your OS packages are up to date:

sudo apt update
sudo apt upgrade

Installing Fail2ban

To install Fail2ban, together with the journal module and nftables, use this command:

sudo apt install -y fail2ban python3-systemd nftables

python3-systemd is only a recommended dependency of fail2ban, so it is skipped if you install with --no-install-recommends. Naming it explicitly makes sure it is there.

Configuring the SSH jail in jail.local

Never edit /etc/fail2ban/jail.conf: package upgrades overwrite it. Older guides (including the first version of this one) copy the whole file to jail.local, which also freezes every default at today’s values. Instead create a short /etc/fail2ban/jail.local that contains only what you change:

sudo vim /etc/fail2ban/jail.local

Paste in the following:

[DEFAULT]
# Never ban localhost or your own admin IP/subnet
ignoreip = 127.0.0.1/8 ::1 203.0.113.5
bantime  = 1h
findtime = 10m
maxretry = 5
banaction = nftables-multiport
banaction_allports = nftables-allports

[sshd]
enabled = true
port    = ssh
backend = systemd

First up are the basic defaults for ignoreip, which allows you to exclude certain IP addresses from being banned. If your own computer or office has a fixed IP, enter it here (space separated, CIDR ranges allowed) so you cannot lock yourself out. Next, bantime determines how long an offending host will remain blocked until automatically unblocked. Lastly findtime and maxretry: a host that fails maxretry times within findtime is banned. Fail2ban accepts suffixes like 10m, 1h and 1d as well as plain seconds.

banaction = nftables-multiport makes bans nftables rules, and backend = systemd in the [sshd] section is the Debian 12 fix: the jail reads sshd’s messages from the journal instead of /var/log/auth.log.

If you have changed the default ssh port make sure to update it as well, otherwise the ban rule blocks port 22 while the attacker keeps hitting your real port:

[sshd]
enabled = true
port    = 3922
backend = systemd

If you have a mail transfer agent configured on your server, you can enable email notifications from Fail2ban by setting destemail and sender in [DEFAULT] and adding action = %(action_mw)s, which bans and sends a whois report.

Jails are the rules which fail2ban applies to any given application or log file. On Debian, the package already enables the sshd jail in /etc/fail2ban/jail.d/defaults-debian.conf; your jail.local is read after it and wins. You can enable any other jail from jail.conf (for example nginx-http-auth or postfix) the same way, by adding a section with enabled = true to jail.local. Jails that read the journal need backend = systemd too.

Test the configuration, then restart the service and enable it at boot:

sudo fail2ban-client -t
sudo systemctl restart fail2ban
sudo systemctl enable fail2ban

fail2ban-client -t should end with OK: configuration test is successful. On Debian 12 it also prints a harmless WARNING 'allowipv6' not defined in 'Definition'. To silence it, create /etc/fail2ban/fail2ban.local with:

[Definition]
allowipv6 = auto

Checking the jail status

List the active jails, then look at the SSH jail:

sudo fail2ban-client status
sudo fail2ban-client status sshd
Status
|- Number of jail:	1
`- Jail list:	sshd
Status for the jail: sshd
|- Filter
|  |- Currently failed:	0
|  |- Total failed:	0
|  `- Journal matches:	_SYSTEMD_UNIT=sshd.service + _COMM=sshd
`- Actions
   |- Currently banned:	0
   |- Total banned:	0
   `- Banned IP list:	

The Journal matches line confirms the jail reads the journal. If it shows File list: instead, the backend = systemd line is not being applied.

You can also check that the filter actually matches the failures already in your journal:

sudo fail2ban-regex systemd-journal sshd

The summary shows how many lines matched; a non-zero matched count after a few failed logins means the filter works.

Testing the ban from a second host

Test from a machine whose IP is not in ignoreip, and keep your current SSH session open in case you ban yourself. From the second host, fail the password a few more times than maxretry:

ssh demo@server.example.com

Back on the server, the jail now lists the attacker:

Status for the jail: sshd
|- Filter
|  |- Currently failed:	1
|  |- Total failed:	6
|  `- Journal matches:	_SYSTEMD_UNIT=sshd.service + _COMM=sshd
`- Actions
   |- Currently banned:	1
   |- Total banned:	1
   `- Banned IP list:	192.0.2.50

/var/log/fail2ban.log records each failure and the ban:

fail2ban.filter         [3059]: INFO    [sshd] Found 192.0.2.50 - 2026-11-10 08:28:29
fail2ban.filter         [3059]: INFO    [sshd] Found 192.0.2.50 - 2026-11-10 08:28:32
fail2ban.actions        [3059]: NOTICE  [sshd] Ban 192.0.2.50

The banned host’s next attempt is rejected before it reaches sshd:

ssh: connect to host server.example.com port 22: Connection refused

Viewing the nftables rules

With the nftables actions, fail2ban creates its own f2b-table table, with a set of banned addresses per jail. View it with:

sudo nft list table inet f2b-table
table inet f2b-table {
	set addr-set-sshd {
		type ipv4_addr
		elements = { 192.0.2.50 }
	}

	chain f2b-chain {
		type filter hook input priority filter - 1; policy accept;
		tcp dport 22 ip saddr @addr-set-sshd reject with icmp port-unreachable
	}
}

The table only appears after the jail starts. Because it lives in its own table, it does not interfere with your own nftables ruleset. On older systems that use the iptables actions, the equivalent is sudo iptables -L -n.

Banning and unbanning IP addresses

You can manually ban and unban IP addresses for any jail with the following commands:

sudo fail2ban-client set <jail> banip <ip address>
sudo fail2ban-client set <jail> unbanip <ip address>
# For example
sudo fail2ban-client set sshd unbanip 192.0.2.50

To see every banned IP across all jails, or lift all bans at once:

sudo fail2ban-client banned
sudo fail2ban-client unban --all

If you lock yourself out, log in from the console or another IP, unban yourself, then add your address to ignoreip and run sudo fail2ban-client reload.

Troubleshooting

  • Have not found any log file for sshd jail: the jail is still looking for /var/log/auth.log. Add backend = systemd to the [sshd] section of jail.local.
  • Failed to initialize any backend for Jail 'sshd': python3-systemd is missing. Install it and restart fail2ban.
  • Bans appear in fail2ban-client status but the attacker can still connect: check the port in the jail matches the port sshd really listens on (sudo ss -tlnp | grep sshd), and that the ban action matches your firewall (nftables actions on Debian 12).
  • Total failed stays at 0 while logins fail: run sudo fail2ban-regex systemd-journal sshd and sudo journalctl -u ssh -n 20 to see whether the failures reach the journal and match the filter.

Fail2ban is a handy addition to the nftables firewall and access control in general, but it only slows down password guessing. The stronger fix is to use SSH keys and set PasswordAuthentication no in /etc/ssh/sshd_config; fail2ban then mostly keeps your logs quiet.

Last updated on Nov 10, 2026 12:49 +0300
comments powered by Disqus
Citizix Ltd
Built with Hugo
Theme Stack designed by Jimmy