Security

Protect Nginx with Fail2ban: bad bots, scanners and auth

Turn on Fail2ban's built-in Nginx jails to ban password guessers, vulnerability scanners and clients that hit your rate limits, then test each filter against your real logs.

IntermediateLinux server5 min read

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_req rate limit.

Newer Fail2ban versions may include more, such as nginx-bad-request. List what you have:

Bash
ls /etc/fail2ban/filter.d/ | grep nginx

Check 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:

Bash
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:

INI
logpath = /var/log/nginx/*error.log

See Nginx access and error logs for more on log locations.

Enable the jails

Add the jails to /etc/fail2ban/jail.local:

Bash
sudo nano /etc/fail2ban/jail.local
INI
[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  = 1h

Notes on each:

  • nginx-botsearch uses a low maxretry because a real visitor rarely requests two scanner paths in a row. A one-day ban is reasonable.
  • nginx-limit-req only works if Nginx already has limit_req set up. Without it, nothing is logged and the jail never fires. See Nginx rate limiting to add it.
  • nginx-http-auth only matters if you use auth_basic somewhere.

Test and reload:

Bash
sudo fail2ban-client -t && sudo fail2ban-client reload

Note: 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:

Bash
sudo fail2ban-regex /var/log/nginx/error.log /etc/fail2ban/filter.d/nginx-botsearch.conf

The 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:

Bash
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.conf

Check it works

Confirm the jails are running:

Bash
sudo fail2ban-client status
sudo fail2ban-client status nginx-botsearch

Watch bans happen live:

Bash
sudo tail -f /var/log/fail2ban.log | grep nginx

To test end to end without risking your own address, request a few scanner paths from another network, such as a phone on mobile data:

Bash
curl -s -o /dev/null -w "%{http_code}\n" http://example.com/phpmyadmin/setup.php

After 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 logpath is 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. For nginx-limit-req, check that Nginx actually has limit_req configured.
  • Real visitors or your own office get banned: add their addresses to ignoreip, raise maxretry, or disable the noisy jail. Unban with fail2ban-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 port does 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.
Was this guide helpful?

Still stuck?

Ask MdmHost AI for a quick answer, or open a ticket and our team will take a look. We are here 24/7.