Open the SSH logs of a VPS exposed to the Internet: you will see hundreds of login attempts a day, with user names like admin, test or ubuntu. These are bots trying passwords in a loop. fail2ban is the tool that shows them the door.
The idea fits in one sentence: fail2ban reads the logs, spots failures, and when the same IP address fails too often, it blocks it in the firewall for a while. Let's see how it works, how to tune it, and above all how to check that it really does its job.
Three pieces: filter, jail, action
fail2ban combines three elements, each in its own folder under /etc/fail2ban/:
- The filter (
filter.d/): regular expressions that recognize a failure line, for example "Failed password for root from 203.0.113.42". There are filters for SSH, nginx, Postfix and dozens of other services. - The action (
action.d/): what to do with an offending address. By default, a firewall rule (iptables or nftables) that drops its packets. - The jail: it ties a filter, a log source and an action together, with its own settings. The
sshdjail watches SSH, annginx-http-authjail would watch a website's passwords.
bantime, findtime, maxretry
Three settings decide everything. With maxretry = 5, findtime = 10m and bantime = 1h, the rule reads: 5 failures within 10 minutes from the same IP, and it is banned for one hour. When the ban ends, fail2ban releases it on its own.
maxretry: the number of failures allowed. Too low and you ban yourself on the slightest typo; 3 to 5 is a good balance.findtime: the time window in which failures are counted. The unitsm,handdare accepted.bantime: how long the ban lasts.-1bans forever, which is rarely a good idea (addresses change hands).
For repeat offenders, bantime.increment = true makes the ban longer each time the same address comes back: one hour the first time, then two, then four, and so on.
Where the configuration lives
Never edit /etc/fail2ban/jail.conf: a package update may overwrite it. Your settings go in /etc/fail2ban/jail.local (or in files under /etc/fail2ban/jail.d/), which are read afterwards and take precedence. On Debian and Ubuntu, the package already enables the sshd jail through jail.d/defaults-debian.conf.
Here is a complete jail.local for SSH. Replace 198.51.100.7 with your static IP address, if you have one: ignoreip lists the addresses that will never be banned.
[DEFAULT]
bantime = 1h
findtime = 10m
maxretry = 5
bantime.increment = true
ignoreip = 127.0.0.1/8 ::1 198.51.100.7
[sshd]
enabled = true
# Reads the systemd journal: needed on Debian 12, which has no /var/log/auth.log
backend = systemd/etc/fail2ban/jail.local. The systemd backend needs the python3-systemd package.# Check the configuration, then reload
sudo fail2ban-client -t
sudo fail2ban-client reloadCheck that it works
A fail2ban that is installed but misconfigured bans nobody, and nothing warns you. Three commands are enough to be sure. First, is the service running, and which jails are active?
sudo systemctl status fail2ban
sudo fail2ban-client statusThen, the details of the SSH jail:
$ sudo fail2ban-client status sshd
Status for the jail: sshd
|- Filter
| |- Currently failed: 2
| |- Total failed: 143
| `- Journal matches: _SYSTEMD_UNIT=sshd.service + _COMM=sshd
`- Actions
|- Currently banned: 3
|- Total banned: 27
`- Banned IP list: 203.0.113.42 192.0.2.77 198.51.100.23Total failed stuck at 0 on a server exposed for days is suspicious: the filter is probably not reading the right log.Finally, if you doubt the filter, fail2ban-regex replays it on the existing logs and counts the matching lines. fail2ban's own log, /var/log/fail2ban.log, shows every ban as it happens.
sudo fail2ban-regex systemd-journal sshd
sudo tail -f /var/log/fail2ban.logUnban an IP address
It happens: a colleague mistypes their password five times, or it is you. To release the address without waiting for the ban to end:
# In one jail
sudo fail2ban-client set sshd unbanip 203.0.113.42
# In every jail
sudo fail2ban-client unban 203.0.113.42
# Manually ban an address that keeps trying
sudo fail2ban-client set sshd banip 203.0.113.42What fail2ban does not do
fail2ban cuts the noise and slows down brute-force attacks, but it does not replace SSH key login. If passwords are disabled, those bots never stood a chance anyway: fail2ban simply sends them to waste their time elsewhere. It does not protect against a flaw in a web application either, and an attack spread over thousands of addresses stays under its thresholds.
Also think about services running in Docker: their ports bypass the usual firewall rules, so a jail protecting them has to act on the DOCKER-USER chain.