fail2ban monitors log files for patterns that indicate malicious activity – failed SSH login attempts, repeated 404s from a single IP, brute force attacks against a WordPress login page – and automatically bans the offending IP addresses by adding firewall rules. It is one of the most widely deployed security tools on Linux servers.
The core concept is simple: if an IP address fails authentication 5 times in 10 minutes, block it for 30 minutes. The thresholds, time windows, and ban durations are all configurable. fail2ban watches the logs, counts the failures, and manages the bans. You define the rules.
How fail2ban works#
fail2ban runs as a background service (daemon) and follows a straightforward loop:
- It reads log files line by line in real time (similar to
tail -f) - Each new line is matched against patterns called “filters” – regular expressions that identify failures
- When a filter matches, fail2ban records the source IP address and increments a counter
- When the counter for an IP reaches the configured threshold (maxretry) within the configured time window (findtime), fail2ban triggers a “ban action”
- The ban action adds a firewall rule (typically an iptables rule) that drops all packets from the banned IP
- After the ban duration (bantime) expires, fail2ban removes the firewall rule and the IP can connect again
The entire process is reactive. fail2ban does not inspect live network traffic. It reads log files after the fact. The attacker’s requests reach your server, generate log entries, and only then does fail2ban notice and respond. This means the first few attempts always get through – the ban only kicks in after the threshold is exceeded.
The components
fail2ban has three main concepts:
Filters define what to look for in log files. A filter is a set of regular expressions that match specific log patterns. For example, the SSH filter matches lines like:
Failed password for invalid user admin from 198.51.100.20 port 45678 ssh2
Jails combine a filter with a log file, thresholds, and an action. A jail says: “Watch this log file with this filter. If an IP matches more than X times in Y seconds, execute this action for Z seconds.”
Actions define what happens when a ban is triggered. The most common action is adding an iptables rule to drop traffic from the IP. Other actions can send email notifications, run custom scripts, or update external blocklists.
Installing fail2ban#
On Ubuntu/Debian
sudo apt update
sudo apt install fail2ban
On CentOS/RHEL
sudo dnf install epel-release
sudo dnf install fail2ban
Start and enable the service
sudo systemctl start fail2ban
sudo systemctl enable fail2ban
The
enable
command ensures fail2ban starts automatically on boot.
Verify it is running
sudo systemctl status fail2ban
You should see
active (running)
.
Configuration#
fail2ban’s configuration is split across several files:
-
/etc/fail2ban/jail.conf– the default configuration (do not edit this file) -
/etc/fail2ban/jail.local– your overrides (create this file) -
/etc/fail2ban/jail.d/– directory for additional jail configurations -
/etc/fail2ban/filter.d/– filter definitions
The
jail.conf
file is overwritten on package updates. Always put your customizations in
jail.local
or in files under
jail.d/
. Settings in
jail.local
override
jail.conf
.
Create jail.local
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
Or create a minimal file with just your overrides:
[DEFAULT]
bantime = 1h
findtime = 10m
maxretry = 5
Default settings
| Setting | Default | Meaning |
|---|---|---|
bantime
| 10m | How long an IP is banned |
findtime
| 10m | The time window for counting failures |
maxretry
| 5 | Number of failures before banning |
ignoreip
| 127.0.0.1/8 | IPs that are never banned |
These defaults mean: if an IP fails 5 times within 10 minutes, ban it for 10 minutes. For most production servers, the defaults are too lenient. A 10-minute ban barely slows down an attacker running automated tools.
Recommended production settings
[DEFAULT]
bantime = 1h
findtime = 10m
maxretry = 5
ignoreip = 127.0.0.1/8 ::1 YOUR_IP_HERE
banaction = iptables-multiport
Replace
YOUR_IP_HERE
with your own IP address or office IP range so you do not accidentally lock yourself out. Use CIDR notation for ranges:
192.168.1.0/24
.
Incremental ban times
fail2ban supports increasing ban durations for repeat offenders:
[DEFAULT]
bantime = 1h
bantime.increment = true
bantime.factor = 2
bantime.maxtime = 1w
With these settings, the first ban is 1 hour, the second is 2 hours, the third is 4 hours, and so on, up to a maximum of 1 week. This is effective against persistent attackers who return after each ban expires.
Common jails#
SSH jail
The SSH jail is enabled by default on most installations. It watches the SSH authentication log for failed login attempts.
[sshd]
enabled = true
port = ssh
logpath = /var/log/auth.log
maxretry = 3
bantime = 1h
On CentOS/RHEL, the log path is
/var/log/secure
instead of
/var/log/auth.log
.
This is the most important jail on any server with SSH access. Automated bots constantly scan the internet for SSH servers and attempt credential stuffing with common username/password combinations. Without fail2ban, a public-facing SSH server accumulates thousands of failed login attempts per day. See SSH Connection Refused: What It Means and How to Fix It for how IP bans interact with SSH connection errors.
WordPress login jail
WordPress login brute force attacks hit
/wp-login.php
and
/xmlrpc.php
. fail2ban can monitor the web server access log for these patterns.
Create a filter file:
sudo nano /etc/fail2ban/filter.d/wordpress-login.conf
[Definition]
failregex = ^<HOST> .* "POST /wp-login\.php
^<HOST> .* "POST /xmlrpc\.php
ignoreregex =
Create the jail in
/etc/fail2ban/jail.local
:
[wordpress-login]
enabled = true
port = http,https
filter = wordpress-login
logpath = /var/log/nginx/access.log
maxretry = 5
findtime = 5m
bantime = 1h
This bans any IP that sends more than 5 POST requests to wp-login.php or xmlrpc.php within 5 minutes. For a deeper look at what bot traffic against WordPress login pages looks like and the damage it causes, see What happens when bots find your WordPress login page.
Nginx HTTP authentication jail
If you use Nginx’s built-in HTTP basic authentication:
[nginx-http-auth]
enabled = true
port = http,https
logpath = /var/log/nginx/error.log
maxretry = 3
bantime = 1h
This jail uses the built-in
nginx-http-auth
filter that ships with fail2ban.
Nginx bad bot jail
Block IPs that generate excessive 404 errors (typically scanners probing for vulnerable files):
Create
/etc/fail2ban/filter.d/nginx-404.conf
:
[Definition]
failregex = ^<HOST> .* "(GET|POST) .* HTTP/.*" 404
ignoreregex = \.(css|js|jpg|jpeg|png|gif|ico|svg|woff|woff2)
The
ignoreregex
excludes 404s for static assets, which are usually legitimate missing files rather than scanning activity.
[nginx-404]
enabled = true
port = http,https
filter = nginx-404
logpath = /var/log/nginx/access.log
maxretry = 20
findtime = 1m
bantime = 30m
A threshold of 20 in 1 minute catches aggressive scanners without banning legitimate visitors who hit a few broken links.
Nginx rate limiting jail
If you use Nginx’s
limit_req
module, failed requests are logged to the error log. fail2ban can watch for the excess:
[nginx-limit-req]
enabled = true
port = http,https
logpath = /var/log/nginx/error.log
maxretry = 10
bantime = 1h
Managing bans#
Check banned IPs
sudo fail2ban-client status sshd
Output:
Status for the jail: sshd
|- Filter
| |- Currently failed: 2
| |- Total failed: 847
| `- File list: /var/log/auth.log
`- Actions
|- Currently banned: 3
|- Total banned: 156
`- Banned IP list: 198.51.100.20 203.0.113.45 192.0.2.100
This shows the current state of the SSH jail: how many IPs are currently banned, total historical bans, and the specific IPs that are currently blocked.
List all active jails
sudo fail2ban-client status
Shows all enabled jails and their ban counts.
Unban a specific IP
sudo fail2ban-client set sshd unbanip 198.51.100.20
Replace
sshd
with the jail name and the IP with the address to unban. This is the command you need when a legitimate user gets locked out. It takes effect immediately.
Unban from all jails at once
sudo fail2ban-client unban 198.51.100.20
Without specifying a jail, this removes the IP from every jail.
Manually ban an IP
sudo fail2ban-client set sshd banip 198.51.100.20
Adds a ban manually without waiting for log matches.
Check if a specific IP is banned
sudo fail2ban-client get sshd banned 198.51.100.20
Returns 1 if banned, 0 if not.
How fail2ban interacts with the firewall#
fail2ban manages firewall rules through its “ban action.” The default action on most Linux systems uses iptables.
When fail2ban bans an IP, it runs a command like:
iptables -I f2b-sshd -s 198.51.100.20 -j REJECT
This inserts a rule in the
f2b-sshd
chain that rejects all traffic from that IP. When the ban expires, fail2ban removes the rule.
You can see the fail2ban rules in your firewall:
sudo iptables -L f2b-sshd -n
fail2ban with nftables
On newer systems using nftables instead of iptables, configure the ban action accordingly:
[DEFAULT]
banaction = nftables-multiport
fail2ban with firewalld
On CentOS/RHEL systems using firewalld:
[DEFAULT]
banaction = firewallcmd-rich-rules
Logging and monitoring#
fail2ban’s own log
fail2ban logs its actions to
/var/log/fail2ban.log
:
tail -50 /var/log/fail2ban.log
Each ban and unban is logged with the jail name, IP address, and timestamp. This log is useful for understanding ban patterns and identifying persistent attackers.
Email notifications
fail2ban can send email notifications on bans:
[DEFAULT]
destemail = admin@yourdomain.com
sender = fail2ban@yourdomain.com
mta = sendmail
action = %(action_mwl)s
The
action_mwl
sends an email with the ban details including whois information and relevant log lines. This is useful on low-traffic servers but generates too much mail on servers under heavy attack.
Troubleshooting#
fail2ban is not banning IPs
Check that the jail is enabled:
sudo fail2ban-client status
If the jail is not listed, it is not enabled. Add
enabled = true
to the jail configuration.
Check that the log file exists and is being written to:
ls -la /var/log/auth.log
tail -5 /var/log/auth.log
If the log file does not exist or is empty, fail2ban has nothing to watch.
Check the filter matches the log format:
sudo fail2ban-regex /var/log/auth.log /etc/fail2ban/filter.d/sshd.conf
This tests the filter regex against the actual log file and reports how many lines match. If the match count is zero, the filter pattern does not match your log format.
fail2ban banned my own IP
This happens when you mistype your password multiple times or when automated tools (backup scripts, monitoring agents) use the wrong credentials.
Unban yourself:
sudo fail2ban-client set sshd unbanip YOUR_IP
Then add your IP to the ignore list to prevent it from happening again:
[DEFAULT]
ignoreip = 127.0.0.1/8 ::1 YOUR_IP_HERE
Restart fail2ban after changing the configuration:
sudo systemctl restart fail2ban
fail2ban is not starting
Check the logs for errors:
sudo journalctl -u fail2ban --no-pager -n 50
Common causes:
- Syntax error in jail.local (missing brackets, wrong indentation)
- A jail references a log file that does not exist
- A filter file has an invalid regular expression
Bans are not persisting after restart
By default, fail2ban loses all active bans when the service restarts. To persist bans across restarts, enable the database:
[DEFAULT]
dbfile = /var/lib/fail2ban/fail2ban.sqlite3
dbpurgeage = 1d
The
dbpurgeage
setting controls how long ban history is kept.
fail2ban vs server-level bot detection#
fail2ban is a reactive tool. It reads logs, counts failures, and bans after the threshold is exceeded. This means:
- The attacker’s first several requests always reach your server
- Each request consumes server resources (PHP-FPM workers, database queries, CPU)
- The ban only takes effect after the damage has started
- Sophisticated attackers rotate IP addresses, staying under the threshold on each one
Server-level bot detection systems work differently. Hostney’s bot detection system scores incoming requests based on behavioral signals before they reach your application: request patterns, TLS fingerprinting, header analysis, and cross-site reputation data. A bot that has been attacking other sites on the platform is already flagged before it sends its first request to your server.
The difference is where and when the decision is made:
| fail2ban | Server-level bot detection | |
|---|---|---|
| When it acts | After failures are logged | Before the request reaches PHP |
| What it sees | Individual log entries | Full request context and cross-site behavior |
| IP rotation | Each new IP starts fresh | Behavioral patterns carry across IPs |
| Resource cost | Attacker’s requests consume PHP workers until banned | Blocked at the edge before consuming application resources |
| Configuration | Manual per-server | Automatic, platform-wide |
Both have a role. On a self-managed VPS where you control the entire stack, fail2ban is essential. It is the standard tool for SSH protection, and its WordPress and Nginx jails catch the most obvious attacks. On a managed platform with built-in bot detection, fail2ban’s role is reduced because the platform handles the heavy lifting at a layer fail2ban cannot reach.
For a comparison of application-level security plugins versus server-level protection, see Wordfence and server-level security: why you need both.
fail2ban on Hostney#
On Hostney, each account runs in an isolated container with server-level bot detection active by default. fail2ban is not available or needed on Hostney’s managed hosting platform because:
- SSH brute force protection is handled at the platform level
- WordPress login attacks are blocked by the bot detection system before they reach PHP
- IP-based rate limiting is enforced at the edge
fail2ban is a tool for servers you manage yourself. If you run a VPS or dedicated server and administer it directly, fail2ban is one of the first things to install. On managed hosting where the provider handles security at the infrastructure level, it is not applicable.