Block malicious IPs with /24 subnet banning for Apache2 web server.
Fail2Ban is a log-driven intrusion-prevention daemon. It watches log files for patterns that indicate abuse — repeated failed logins, probes for PHP admin panels, spam bots hitting a search endpoint — and temporarily blocks the offending addresses in the host firewall. This guide configures it to ban whole /24 subnets for an Apache web server, then explains how the pieces work and where the approach has limits.
sudo apt update
sudo apt install fail2ban -y
Create a custom action that bans entire /24 subnets (256 IPs) instead of individual IPs:
sudo vim /etc/fail2ban/action.d/iptables-subnet.conf
Add the following content:
[Definition]
actionban = iptables -I INPUT -s <ip>/24 -j DROP
actionunban = iptables -D INPUT -s <ip>/24 -j DROP
Create a filter to catch spam bots hitting search endpoints:
sudo vim /etc/fail2ban/filter.d/apache-search.conf
Add the following content:
[Definition]
failregex = ^<HOST> .* "(GET|POST) /search\?.*"
ignoreregex =
Create a filter to catch any request containing "php" in the URL:
sudo vim /etc/fail2ban/filter.d/apache-php.conf
Add the following content:
[Definition]
failregex = ^<HOST> .* "(GET|POST) .*php.*"
ignoreregex =
This filter will catch:
/hello.php/wp-admin/something.php?id=1/phpMyAdmin//something/php/test.htmlCreate the jail configuration file:
sudo vim /etc/fail2ban/jail.local
Add the following content:
[apache-search]
enabled = true
port = http,https
filter = apache-search
logpath = /var/log/apache2/protonchat.access.log
backend = auto
maxretry = 1
findtime = 86400
bantime = 432000
banaction = iptables-subnet
[apache-php]
enabled = true
port = http,https
filter = apache-php
logpath = /var/log/apache2/protonchat.access.log
backend = auto
maxretry = 1
findtime = 86400
bantime = 432000
banaction = iptables-subnet
sudo systemctl enable fail2ban
sudo systemctl restart fail2ban
# List all jails
sudo fail2ban-client status
# Check specific jail
sudo fail2ban-client status apache-search
sudo fail2ban-client status apache-php
sudo fail2ban-regex /var/log/apache2/protonchat.access.log /etc/fail2ban/filter.d/apache-search.conf
sudo fail2ban-regex /var/log/apache2/protonchat.access.log /etc/fail2ban/filter.d/apache-php.conf
sudo iptables -L INPUT -n | grep "/24"
Expected output:
DROP 0 -- 103.3.220.0/24 0.0.0.0/0
DROP 0 -- 102.89.75.0/24 0.0.0.0/0
DROP 0 -- 125.164.122.0/24 0.0.0.0/0
# View all banned IPs for a jail
sudo fail2ban-client get apache-search banned
# View jail status with ban list
sudo fail2ban-client status apache-search
# Unban a specific IP
sudo fail2ban-client set apache-search unbanip 1.2.3.4
# Unban all IPs from all jails
sudo fail2ban-client unban --all
# Watch fail2ban activity in real-time
sudo tail -f /var/log/fail2ban.log
/etc/fail2ban/jail.conf (do not edit)/etc/fail2ban/jail.local/etc/fail2ban/filter.d//etc/fail2ban/action.d//var/log/fail2ban.logFail2Ban runs as a server process (fail2ban-server) controlled by fail2ban-client. Each jail ties together one or more log paths, a filter and an action. The filter is a set of Python regular expressions in which the placeholder <HOST> captures the client address. When a log line matches failregex and not ignoreregex, the jail records a failure for that host with the line's timestamp. If the host accumulates maxretry failures inside a sliding window of findtime seconds, the jail calls the action's actionban command with the address substituted for <ip>. After bantime seconds it calls actionunban.
The custom action in step 2 turns the single address into a /24 by appending the prefix length. iptables accepts host bits in the source and masks them, so 203.0.113.57/24 becomes a rule for 203.0.113.0/24. Inserting with -I places the rule at the top of INPUT so it takes effect before any accept rules.
The configuration above uses a 24-hour window and a five-day ban with a single strike. That is aggressive and suits requests no legitimate visitor makes, such as PHP paths on a site that serves no PHP. For SSH or login forms, a more forgiving setting such as maxretry = 5, findtime = 600 avoids locking out users who mistype a password. Fail2Ban also offers a recidive jail that watches its own log and applies long bans to addresses that are banned repeatedly.
| Choice | Benefit | Cost |
|---|---|---|
| Ban /24 instead of single IP | Stops bots rotating through neighbouring addresses | Can block innocent users behind the same ISP or cloud range |
| maxretry = 1 | Immediate response to obvious probes | No tolerance for a mistaken URL |
| Long bantime | Fewer repeat hits in logs | Larger iptables chain, slower to recover from a false positive |
| Log-based detection | Works with any service that logs | Reacts after the request has been served |
Fail2Ban complements, rather than replaces, a default-deny firewall, application authentication and rate limiting. It is reactive by design: the first offending request is always served. For high-volume attacks, ipset-based actions (such as the bundled iptables-ipset or nftables actions) scale better than one iptables rule per ban because lookups are hashed rather than linear.
ignoreip in the [DEFAULT] section of jail.local before enabling aggressive jails.fail2ban-regex first./var/log/fail2ban.log after restarting.fail2ban-client set <jail> unbanip.To remove Fail2Ban and its rules cleanly, see the Fail2Ban uninstall guide. For cloud-level filtering in front of a server, see AWS Network Security.