Every public web server is probed all day by scanners looking for /wp-login.php, .env files and old admin panels. Fail2ban can read your Nginx logs and ban those clients at the firewall, before they reach PHP or your app. This guide enables the three Nginx jails that ship with Fail2ban, points them at the right logs and tests each one.
Before you start
- A Linux VPS running Nginx on Ubuntu 22.04 or 24.04, Debian 12, or AlmaLinux or Rocky Linux 8 or 9. See Install Nginx on Linux if needed.
- Fail2ban installed and a working
/etc/fail2ban/jail.local. See Configure Fail2ban jails. - Your own IP address in
ignoreip. Web jails can ban you while you test your own site. - SSH allowed in your firewall. Fail2ban adds ban rules on top of your firewall; it does not replace it.
Warning: A ban blocks the address on the ports listed in the jail. If you set port = all or use an all-ports action, a ban also cuts off SSH from that IP. If you ban yourself, log in through the browser console in your VPS provider's control panel and unban your address.
The built-in Nginx jails
Fail2ban ships filters for Nginx in /etc/fail2ban/filter.d/:
- nginx-http-auth: bans clients that fail HTTP basic authentication (
auth_basic) too often. Useful if you protect an admin area with a password. - nginx-botsearch: bans clients requesting paths that do not exist and are typical of scanners, such as admin panels and setup scripts.
- nginx-limit-req: bans clients that keep tripping Nginx's own
limit_reqrate limit.
Newer Fail2ban versions may include more, such as nginx-bad-request. List what you have:
ls /etc/fail2ban/filter.d/ | grep nginxCheck where Nginx logs
The jails read the Nginx error log by default, through the %(nginx_error_log)s variable, which points to /var/log/nginx/error.log. Confirm your sites actually log there:
sudo grep -R "error_log\|access_log" /etc/nginx/ | grep -v '#'If a site logs to its own file, such as /var/log/nginx/example.com.error.log, give the jail that path. A wildcard covers several sites:
logpath = /var/log/nginx/*error.logSee Nginx access and error logs for more on log locations.
Enable the jails
Add the jails to /etc/fail2ban/jail.local:
sudo nano /etc/fail2ban/jail.local[nginx-http-auth]
enabled = true
port = http,https
logpath = %(nginx_error_log)s
maxretry = 5
[nginx-botsearch]
enabled = true
port = http,https
logpath = %(nginx_error_log)s
maxretry = 2
findtime = 10m
bantime = 1d
[nginx-limit-req]
enabled = true
port = http,https
logpath = %(nginx_error_log)s
maxretry = 10
findtime = 1m
bantime = 1hNotes on each:
nginx-botsearchuses a lowmaxretrybecause a real visitor rarely requests two scanner paths in a row. A one-day ban is reasonable.nginx-limit-reqonly works if Nginx already haslimit_reqset up. Without it, nothing is logged and the jail never fires. See Nginx rate limiting to add it.nginx-http-authonly matters if you useauth_basicsomewhere.
Test and reload:
sudo fail2ban-client -t && sudo fail2ban-client reloadNote: If your site sits behind Cloudflare or another proxy, Nginx logs the proxy's address, not the visitor's. Fail2ban would then ban the proxy and break your site for many people. Set up real IP logging first (Nginx real IP behind Cloudflare), and remember that banning at your server firewall does not stop traffic that arrives through the proxy's addresses.
Test filters with fail2ban-regex
Before trusting a jail, run its filter against your real log. fail2ban-regex reads a log file and reports how many lines a filter matches, without banning anything:
sudo fail2ban-regex /var/log/nginx/error.log /etc/fail2ban/filter.d/nginx-botsearch.confThe end of the output shows the result:
Lines: 18432 lines, 0 ignored, 37 matched, 18395 missed- Matched above zero: the filter recognises lines in your log. Good.
- Zero matched on a busy server: the log path or format does not fit the filter. Check that you tested the right file.
To see exactly which lines matched, add --print-all-matched. Look through them for real visitors. If a normal page shows up, adjust maxretry or skip that jail.
Repeat for the other two filters:
sudo fail2ban-regex /var/log/nginx/error.log /etc/fail2ban/filter.d/nginx-http-auth.conf
sudo fail2ban-regex /var/log/nginx/error.log /etc/fail2ban/filter.d/nginx-limit-req.confCheck it works
Confirm the jails are running:
sudo fail2ban-client status
sudo fail2ban-client status nginx-botsearchWatch bans happen live:
sudo tail -f /var/log/fail2ban.log | grep nginxTo test end to end without risking your own address, request a few scanner paths from another network, such as a phone on mobile data:
curl -s -o /dev/null -w "%{http_code}\n" http://example.com/phpmyadmin/setup.phpAfter the limit, that address should appear in the jail's banned list, and further requests from it should time out.
Troubleshooting
- Jail fails to start with "Have not found any log file": the
logpathis wrong or the file does not exist yet. Point it at the real error log and reload. - Jail runs but never bans: test with
fail2ban-regex. If nothing matches, the filter does not fit your log. Fornginx-limit-req, check that Nginx actually haslimit_reqconfigured. - Real visitors or your own office get banned: add their addresses to
ignoreip, raisemaxretry, or disable the noisy jail. Unban withfail2ban-client set nginx-botsearch unbanip IP. - Bans show Cloudflare addresses: real IP logging is missing. Fix it before running web jails, then unban those addresses.
- Scanners still reach the site after a ban: they arrive through a proxy or CDN, or the jail's
portdoes not include the port your site uses.
Next steps
- Learn more ways to block scanners: Block bad bots in Nginx.
- Protect a WordPress login: Fail2ban for Nginx and WordPress.
- Need a filter for your own app? Write a custom Fail2ban filter.
