In short: Fail2ban reads log files and bans addresses in the firewall that stand out too often within a time window. For Nginx and Apache it comes with ready-made jails such as nginx-botsearch or apache-noscript; test your own filters with fail2ban-regex. Settings belong in jail.local, your own addresses in ignoreip, and the recidive jail catches repeat offenders. Fail2ban does not help against individual targeted attacks, but it does against mass scans.
When the logs are full of scanners
Anyone looking at their web servers' access logs these days often finds them full of requests for paths that do not exist at all: old admin interfaces, test files, known vulnerabilities of other products. In a situation like PHP2Shell, targeted searching for vulnerable servers comes on top: Cloudflare and Patchstack reported attack attempts, and Unit 42 counted more than 22.6 million exposed, potentially vulnerable instances.
Fail2ban is a proven tool against this mass. It reads log files, uses filters to recognize suspicious entries and bans the sender address in the firewall for a defined time. This relieves the server, reduces log noise and makes systematic trial and error from one source more laborious.
What Fail2ban cannot do is just as important: a single request that exploits a vulnerability is not prevented. Against PHP2Shell, only the PHP update helps. How automated attacks work is described in Mass scans for unpatched PHP.
Fail2ban for Nginx and Apache: the bundled jails
A jail connects a filter with a log file and a ban action. The bundled jail.conf already contains jails for web servers. They are disabled by default and are enabled individually.
| Jail | Web server | Detects |
|---|---|---|
nginx-http-auth |
Nginx | failed HTTP Basic logins |
nginx-limit-req |
Nginx | requests that exceed Nginx's rate limit |
nginx-botsearch |
Nginx | search for typical, non-existent paths |
nginx-bad-request |
Nginx | malformed requests |
apache-auth |
Apache | failed logins |
apache-badbots |
Apache | known malicious bot identifiers |
apache-noscript |
Apache | calls to non-existent scripts |
apache-overflows |
Apache | overlong requests |
apache-botsearch |
Apache | search for typical, non-existent paths |
php-url-fopen |
both | certain patterns of URL inclusion attempts |
recidive |
both | addresses that Fail2ban has banned repeatedly |
The default values of the bundled configuration are rather mild: a ban for 10 minutes after 5 hits in 10 minutes. For web servers with a lot of scan traffic, a longer ban and an increasing factor for repeats are worthwhile.
The basic configuration in jail.local
The jail.conf itself says it should not be modified because updates replace it. Your own values belong in /etc/fail2ban/jail.local. The following example for Nginx sets sensible defaults and enables suitable jails:
# /etc/fail2ban/jail.local
[DEFAULT]
# Enter your own fixed addresses (replace the example address)
ignoreip = 127.0.0.1/8 ::1 203.0.113.10
bantime = 1h
findtime = 10m
maxretry = 5
bantime.increment = true
[nginx-http-auth]
enabled = true
[nginx-botsearch]
enabled = true
[recidive]
enabled = true
bantime = 1w
findtime = 1d
For Apache, enable apache-auth, apache-noscript and apache-botsearch instead. Fail2ban supplies the log paths per distribution; if your paths differ, set logpath in the respective jail. If your system reads logs only via the journal, backend = systemd helps.
A custom filter for mass requests for PHP files
A typical pattern in the current situation is many requests in a short time for PHP files that do not exist. A custom filter recognizes this by the 404 status code. It describes only what is in your own log:
# /etc/fail2ban/filter.d/nginx-php-404.conf
[Definition]
failregex = ^<HOST> -.*"(GET|POST|HEAD) [^"]*\.php[^"]*" 404
ignoreregex =
# Addition in /etc/fail2ban/jail.local
[nginx-php-404]
enabled = true
port = http,https
filter = nginx-php-404
logpath = /var/log/nginx/access.log
maxretry = 20
findtime = 1m
bantime = 1h
Before you enable the filter, test it against the real log. That way you see how many lines it hits and whether legitimate requests are among them:
fail2ban-regex /var/log/nginx/access.log /etc/fail2ban/filter.d/nginx-php-404.conf
sudo systemctl restart fail2ban
sudo fail2ban-client status
sudo fail2ban-client status nginx-php-404
# Release an address that was banned by mistake
sudo fail2ban-client set nginx-php-404 unbanip 203.0.113.25
Choose the threshold generously. A crawler working through outdated links, or a monitoring service, must not end up banned.
The most common pitfalls
Fail2ban is quickly installed but easily misused. Check these points before productive use:
- Locking yourself out: Office, VPN and monitoring addresses belong in
ignoreipbefore jails become active. - Upstream services: Behind a CDN or load balancer, Fail2ban without adjustment sees only their addresses. More on this in the guide Protecting admin logins against brute force.
- Log rotation: After the log file changes, Fail2ban must read the new file. After the first rotation, check that the jails keep counting hits.
- Interaction with the firewall: Fail2ban enters bans into the firewall. Anyone who manages their firewall by fully reloading the rule set can delete these entries in the process. How to build the firewall itself is covered under Firewall for web servers: opening only necessary ports.
- IPv6: Attacks also come via IPv6. Check whether your ban action covers both protocols.
Evaluate the ban lists regularly. If bans pile up from particular networks or for particular paths, that is a sign of targeted searching and a reason to look more closely at the affected application.
Conclusion: a tool against the mass, not against everything
Fail2ban is quickly set up and takes part of the constant bombardment off web servers. The bundled jails cover typical patterns, custom filters can be tested safely against real logs with fail2ban-regex, and the recidive jail ensures persistent sources stay out longer.
You should not overestimate it. Fail2ban reacts to repetition, not to the first hit, and it sees only what is in the logs. Against a vulnerability like PHP2Shell only the update helps; against distributed attacks from many addresses Fail2ban helps hardly at all. It unfolds its value as one layer among several: alongside a lean firewall, a hardened web server configuration, two-factor login and monitoring that not only blocks anomalies but also reports them.
How does PHP SOS help?
- Evaluate logs: We look at which patterns occur in your access logs and select suitable jails.
- Set up and test: We set up Fail2ban with custom filters, test every filter against real logs and enter your fixed addresses as exceptions.
- Clarify the interplay: We coordinate CDN, load balancer, firewall and log rotation so that bans hit the right addresses.
- Observe: As part of security monitoring, we continuously evaluate bans and anomalies.
Frequently asked questions
Does Fail2ban protect against PHP2Shell?
No. Fail2ban reacts only after an address has stood out in the logs several times. A single successful attack is not prevented by it. Fail2ban does, however, reduce the load from scanners and makes repeated attempts from the same source more costly.
Why shouldn't I change jail.conf directly?
The file is replaced during package updates, and it says so itself. Your own settings belong in a jail.local or in files under jail.d. There they override the default values selectively.
What is the recidive jail?
A jail that evaluates Fail2ban's own log. Anyone who is banned again and again receives a much longer ban. In the bundled configuration it is one week.
Does Fail2ban work behind Cloudflare or a load balancer?
Only with additional configuration. Without it the logs contain the address of the upstream service, and a ban would hit all visitors. The web server must then take over the real visitor address, and bans are better set at the upstream service.