fail2ban: automating the lockouts iptables doesn't
A short walkthrough of fail2ban's mental model, the minimum useful config, and how it complements iptables and a hardened SSH setup.
Why fail2ban Exists
iptables decides who is allowed to connect. SSH hardening decides what they can do if they reach you. Neither of them addresses the in-between problem: an attacker who is technically allowed to reach port 22 and is now spending all day guessing passwords.
You could solve this manually: tail journalctl, watch for failed logins, write a rule. Nobody actually does that. fail2ban automates exactly that loop. Read a log, match a pattern, push a temporary firewall rule against the offending IP, expire the rule later.
That’s the entire mental model. Everything else is configuration.
How It Actually Works
Three pieces:
- A filter is a regex that matches “this log line means a failed authentication.” Pre-shipped for SSH, nginx, postfix, and dozens of others.
- A jail binds one filter to a backend (which log to watch), a threshold (how many matches before action), and an action (what to do).
- An action is usually an
iptables/nftablesrule that blocks the source IP, plus a timer that reverses it.
fail2ban-server runs as a daemon, tails the relevant logs, and applies actions when thresholds are hit. fail2ban-client is how you talk to it.
Minimum Useful Config
The default install does almost nothing until you enable a jail. Override the defaults in /etc/fail2ban/jail.local (never edit jail.conf directly; it gets overwritten by package updates):
1
2
3
4
5
6
7
8
9
[DEFAULT]
bantime = 1h
findtime = 10m
maxretry = 5
backend = systemd
ignoreip = 127.0.0.1/8 ::1 100.64.0.0/10
[sshd]
enabled = true
What this says:
- Five failed attempts within ten minutes earns a one-hour ban.
- Logs come from the systemd journal, not a flat file. Relevant on any modern Fedora, Debian, or Ubuntu.
ignoreipexempts loopback and (in my case) the entire Tailscale CGNAT range. I never want a typo on my own tailnet to lock me out.- The
[sshd]jail uses the built-in filter at/etc/fail2ban/filter.d/sshd.conf.
Enable and start:
1
sudo systemctl enable --now fail2ban
Verify it’s watching the right thing:
1
sudo fail2ban-client status sshd
You’ll see something like:
1
2
3
4
5
6
7
8
9
Status for the jail: sshd
|- Filter
| |- Currently failed: 0
| |- Total failed: 0
| `- File list: (systemd journal)
`- Actions
|- Currently banned: 0
|- Total banned: 0
`- Banned IP list:
That’s a working jail with no current offenders. On a server with port 22 exposed to the internet, the “Total banned” counter will start climbing within hours.
Watching It Work
The most informative single command:
1
sudo fail2ban-client status sshd
Lists currently-banned IPs and lifetime totals for that jail.
To unban an IP manually (yourself, after a self-inflicted lockout):
1
sudo fail2ban-client set sshd unbanip 203.0.113.42
To see what rules are currently active in the firewall from fail2ban’s actions:
1
sudo iptables -L f2b-sshd -n --line-numbers
The f2b-sshd chain is created by fail2ban on first ban and holds the active blocks. When a ban expires, the corresponding line is removed automatically.
Gotchas Worth Knowing
Bans don’t persist across restarts by default. Adding bantime.increment = true and tuning bantime.maxtime makes repeat offenders earn exponentially longer bans, which is closer to what you actually want.
The systemd backend reads from the journal, not /var/log/auth.log. If you’ve configured rsyslog to forward auth events and were expecting the file-based filter to work, it won’t. You would be silently watching nothing.
ignoreip is your safety net. If you’re remote and rely on a VPN or jump host to reach the machine, exempt that range explicitly. Locking yourself out of a remote host because of a sticky keyboard is a fixable mistake but an annoying one.
Don’t run fail2ban without iptables/nftables loaded. The action layer needs something to install rules into. On a minimal container or stripped-down system, you may need firewalld or raw iptables available first.
Where It Fits
In layers:
- iptables: default-deny on inbound, only the ports you actually serve.
- SSH hardening: keys only, no root login, no password auth, port 22 closed if you can manage it.
- fail2ban: catch what gets through anyway.
Each layer assumes the previous one is in place. fail2ban is not a substitute for either of the others; it’s the alarm system you install after the door is already locked. But it’s cheap to set up, costs nothing to run, and turns a constant stream of probe attempts into a problem that solves itself.