You need a Linux machine where you can use sudo, and
you should not practise on a server anyone depends on. The commands
here change real system state. A spare machine, a virtual machine, or a
cloud instance you can rebuild is the right place; a laptop you own is
fine too. If the terminal itself is new to you, do
Introduction to the
Linux Terminal first — it takes about ten minutes and everything
below assumes it. Every step tells you how to undo it.
What Fail2ban Does
Fail2ban watches log files for patterns that indicate failure — repeated bad SSH logins, web authentication failures, mail relay attempts — and when one source crosses a threshold, it adds a firewall rule blocking that address for a while.
It is a noise reducer and a slow-down, not a wall. If you have already disabled password authentication on SSH, brute force cannot succeed anyway; Fail2ban then mainly keeps your logs readable and reduces wasted resources. That is still worth having, but it should be layered after proper authentication, never instead of it.
A filter is a regex that recognises a failure in a log. A jail ties a filter to a log source, a threshold and a duration. An action is what happens on a match — normally inserting a firewall rule.
Install and Configure
sudo apt install fail2ban
systemctl status fail2ban
jail.conf.
It is replaced on every package upgrade and your changes vanish. Create
/etc/fail2ban/jail.local instead — it overrides jail.conf and is
never overwritten. The same applies to individual jail files under
jail.d/.
sudo nano /etc/fail2ban/jail.local
[DEFAULT]
bantime = 1h
findtime = 10m
maxretry = 5
ignoreip = 127.0.0.1/8 ::1 192.168.1.0/24
backend = systemd
[sshd]
enabled = true
maxretry = 3
bantime = 1d
findtime— the window in which failures are countedmaxretry— failures within that window before a banbantime— how long the ban lasts.-1is permanent, which builds an ever-growing rule set — prefer a long finite timeignoreip— never ban these. Put your own network herebackend = systemd— read the journal rather than a log file. On current Debian and Ubuntu this is the correct setting, because/var/log/auth.logmay not exist at all
Progressive banning punishes repeat offenders without affecting anyone else:
bantime.increment = true
bantime.factor = 2
bantime.maxtime = 5w
sudo systemctl restart fail2ban
Do Not Ban Yourself
This is the classic Fail2ban incident, and it is entirely avoidable.
- Put your static address or LAN range in
ignoreipbefore enabling anything - Have out-of-band access — a provider console or physical access — before you rely on this on a remote machine
- Know the unban command in advance (below), so a mistake costs a minute
- Remember dynamic addresses change — an
ignoreipentry from home may not describe you tomorrow
Checking It Actually Works
A running service is not evidence that anything is being blocked. Verify the jail, then verify the firewall.
sudo fail2ban-client status # which jails are active
sudo fail2ban-client status sshd # failures, bans, and current banned list
The sshd status output gives you the numbers that matter:
Filter
| |- Currently failed: 2
| `- Total failed: 417
Actions
|- Currently banned: 3
`- Total banned: 58
Then confirm the ban reached the firewall — this is the step people skip:
sudo iptables -S | grep f2b # iptables backend
sudo nft list ruleset | grep f2b # nftables backend
Manual control:
sudo fail2ban-client set sshd unbanip 203.0.113.9
sudo fail2ban-client set sshd banip 203.0.113.9
sudo fail2ban-client unban --all
Testing a Filter Before Trusting It
If you write or adapt a filter, test the regex against real log data rather than assuming it matches:
fail2ban-regex /var/log/auth.log /etc/fail2ban/filter.d/sshd.conf
fail2ban-regex systemd-journal /etc/fail2ban/filter.d/sshd.conf
This reports how many lines matched. A filter that matches zero lines is a jail that will never ban anyone, and it will sit there looking perfectly healthy — the exact failure mode this command exists to catch.
Other Jails Worth Enabling
Only enable jails for services you actually run — a jail pointed at a log that does not exist just fails to start.
[nginx-http-auth]
enabled = true
[nginx-botsearch]
enabled = true
[postfix]
enabled = true
[recidive]
enabled = true
bantime = 4w
findtime = 1d
maxretry = 5
recidive is the useful meta-jail: it watches Fail2ban's own log and
applies a long ban to addresses that keep getting banned by other jails.
If your server has IPv6 connectivity, confirm the jails and actions handle it. A host reachable over IPv6 with IPv4-only banning is only half protected — and the attacker does not have to choose IPv4.
Now Do It Yourself: Five Steps
Fail2ban does one thing: it reads a log, matches a pattern, and bans the address
it finds. Understanding it means understanding that pattern — so step 2 tests
the matching for real, on real log lines, using nothing but grep. Get
that right and the rest is configuration.
Go: open a terminal on the server.
Do: run systemctl status fail2ban, then sudo fail2ban-client status.
You should see: the service reported as active (running), and then a list of jails — typically just sshd on a fresh install. Those are two different questions and both matter: a running daemon with no jails enabled protects nothing.
If not: Unit fail2ban.service could not be found means it is not installed — sudo apt install fail2ban on Debian and Ubuntu. If it is installed but inactive, start and enable it: sudo systemctl enable --now fail2ban. Installing alone does not start it, and starting alone does not survive a reboot.
Go: anywhere you can run grep. This step needs no fail2ban at all and every output below was captured for real.
Do: make a sample log line and test the core of fail2ban's sshd pattern against it.
printf 'Aug 20 03:11:09 host sshd[113]: Failed password for root from 198.51.100.4 port 40222 ssh2\n' > sample.log
grep -oE 'Failed password for (invalid user )?\S+ from [0-9.]+' sample.log
You should see: Failed password for root from 198.51.100.4. That is the whole idea — fail2ban's filter is a regular expression with a placeholder where the address sits, and the address it captures is the one it bans.
If not: 🔴 Try it with a capital P — grep -cE 'Failed Password for' sample.log returns 0, while the lowercase version returns a match. A filter that matches nothing produces no error, no warning and no bans; it simply sits there looking configured. That is the single most common way a fail2ban installation does nothing for months.
Go: on the server, create /etc/fail2ban/jail.local. 🔴 Do not edit jail.conf. It is replaced on every package upgrade and your changes vanish silently; jail.local overrides it and is never touched.
Do: put this in jail.local, replacing the address with your own, then reload with sudo systemctl reload fail2ban.
[DEFAULT]
ignoreip = 127.0.0.1/8 203.0.113.5
bantime = 1h
findtime = 10m
maxretry = 5
[sshd]
enabled = true
You should see: the reload complete without complaint. Read the three numbers together: 5 failures within 10 minutes earns a 1 hour ban. ignoreip is your seatbelt — put your own fixed address there so you cannot ban yourself.
If not: if the reload fails, the file has a syntax problem — section headers in square brackets, one key = value per line, no quotes. And if your own address is dynamic, ignoreip cannot save you; rely instead on key-only SSH, which never produces a failed password at all.
Go: same terminal.
Do: run sudo fail2ban-client status sshd.
You should see: the jail's file list, the number of failures it has seen, and the currently banned addresses. If the file list is empty or points somewhere that does not exist, the jail is running and reading nothing.
If not: the usual cause is a systemd-only machine with no /var/log/auth.log at all — there, the jail needs backend = systemd so it reads the journal instead of a file. Check whether that file exists before assuming the jail is broken. The exact wording of these fail2ban-client outputs was not captured while writing this page, because fail2ban is not installed on the machine it was written on. The commands and the configuration are the standard ones; step 2, which is the part that teaches the mechanism, was executed for real.
Go: same terminal. Do this now, while you still have access.
Do: run sudo fail2ban-client set sshd unbanip 198.51.100.4 to practise the command shape on an address that is not banned.
You should see: a numeric reply. Remember this command, because the day you need it you will be locked out and unable to look it up on the machine that has the answer.
If not: 🔴 If you ban yourself, fail2ban is not the problem — reaching the server is. Recovery means a console through your hosting provider, or physical access, exactly as with a firewall lockout. Three habits prevent it: put your address in ignoreip, use key-only SSH so a mistyped password cannot count against you, and keep a second session open whenever you change any of this.
Without scrolling up: fail2ban is installed, running, and has banned nobody in three
months. Is that good news? Answer: not necessarily. It may mean nobody is attacking
you, or it may mean the filter matches nothing at all — which produces no error.
Check the failure count with fail2ban-client status sshd: zero failures
seen on a public server means the jail is not reading what you think.
Now do it without the page: take three real lines from your own
authentication log and test them against the pattern from step 2 with
grep -oE. If your distribution words the message differently, adjust the
pattern until it captures the address — that is exactly the work of writing a
custom fail2ban filter.
Summary
- Filter, jail, action — that is the whole model
- Configure in
jail.local, neverjail.conf backend = systemdon modern distros, where the log file may not exist- Set
ignoreipbefore enabling and know the unban command in advance - Verify bans reach the firewall — a running service proves nothing
- Layer it after key-only SSH, never instead of it
Run sudo fail2ban-client status sshd and check that "Total banned" is
greater than zero on an internet-facing host. If it is still zero after a day, the jail
is not matching — run fail2ban-regex and find out why, rather than assuming
you are simply unpopular.