Fail2Ban is best known for protecting SSH, but it can watch any log file. For web servers that means banning IPs that brute-force login pages, probe for vulnerable paths, hammer password-protected areas or keep tripping Nginx’s rate limits. This guide sets up practical Nginx jails, including custom filters.

If Fail2Ban isn’t installed yet, start with installing and configuring Fail2Ban.

Where the bans happen

Fail2Ban reads /var/log/nginx/access.log and error.log, matches patterns (filters), and bans offending IPs in the firewall (iptables, nftables, UFW or firewalld actions). Put your jails in /etc/fail2ban/jail.local or /etc/fail2ban/jail.d/nginx.conf, never in jail.conf.

Behind Cloudflare or another proxy? Your logs must contain the real client IP, not the proxy’s (set_real_ip_from + real_ip_header CF-Connecting-IP). Bans in your server’s firewall also won’t block traffic that arrives through the proxy. Use a Cloudflare action, or ban at the proxy instead. See real visitor IPs behind Cloudflare.

Built-in Nginx filters

Fail2Ban ships several filters in /etc/fail2ban/filter.d/:

Filter Catches
nginx-http-auth Failed HTTP basic auth (from error.log)
nginx-limit-req Requests rejected by limit_req (from error.log)
nginx-botsearch Requests for common exploit paths that 404
nginx-bad-request Malformed requests (400)
# /etc/fail2ban/jail.d/nginx.conf
[nginx-http-auth]
enabled  = true
port     = http,https
logpath  = /var/log/nginx/error.log

[nginx-limit-req]
enabled  = true
port     = http,https
logpath  = /var/log/nginx/error.log
maxretry = 10
findtime = 1m
bantime  = 1h

[nginx-botsearch]
enabled  = true
port     = http,https
logpath  = /var/log/nginx/access.log
maxretry = 5

nginx-limit-req pairs naturally with Nginx rate limiting. Nginx rejects excess requests, and Fail2Ban bans IPs that keep doing it. See Nginx rate limiting and Nginx basic auth.

Custom filter: vulnerability scanners

Bots constantly probe for .env, wp-config.php.bak, phpmyadmin, .git and similar. Ban them quickly:

# /etc/fail2ban/filter.d/nginx-scanners.conf
[Definition]
failregex = ^<HOST> -.*"(GET|POST|HEAD) /(\.env|\.git/|wp-config\.php\.(bak|old|save)|phpmyadmin|pma|admin\.php|xmlrpc\.php|vendor/phpunit|cgi-bin/).*" (403|404|444)
ignoreregex =
[nginx-scanners]
enabled  = true
port     = http,https
filter   = nginx-scanners
logpath  = /var/log/nginx/access.log
maxretry = 2
findtime = 10m
bantime  = 24h

Adjust the list to your site. Don’t include paths you actually use. For example, keep xmlrpc.php out of it if Jetpack or an app needs XML-RPC.

Custom filter: login brute force

Generic POST-to-login detection, for example for WordPress:

# /etc/fail2ban/filter.d/nginx-wp-login.conf
[Definition]
failregex = ^<HOST> -.*"POST /wp-login\.php
ignoreregex =
[nginx-wp-login]
enabled  = true
port     = http,https
filter   = nginx-wp-login
logpath  = /var/log/nginx/access.log
maxretry = 6
findtime = 5m
bantime  = 2h

This counts all login POSTs, successful ones included. Choose maxretry so that normal logins never reach it. A more precise WordPress setup (logging only failed logins) is in Fail2Ban for WordPress.

Test filters before enabling them

fail2ban-regex shows how many log lines a filter would match:

sudo fail2ban-regex /var/log/nginx/access.log /etc/fail2ban/filter.d/nginx-scanners.conf
sudo fail2ban-regex --print-all-matched /var/log/nginx/access.log /etc/fail2ban/filter.d/nginx-wp-login.conf | head

Check the matches. If legitimate traffic shows up, refine the regex.

Then reload and verify:

sudo fail2ban-client reload
sudo fail2ban-client status
sudo fail2ban-client status nginx-scanners

Don’t ban yourself or your services

# /etc/fail2ban/jail.local
[DEFAULT]
ignoreip = 127.0.0.1/8 ::1 203.0.113.50 10.0.0.0/24

Add your office IP, monitoring services and any internal servers that call your site. Unban if needed:

sudo fail2ban-client set nginx-scanners unbanip 198.51.100.7

Ban repeat offenders longer

The recidive jail watches Fail2Ban’s own log and bans IPs that keep getting banned, for a week:

[recidive]
enabled  = true
logpath  = /var/log/fail2ban.log
bantime  = 1w
findtime = 1d
maxretry = 3

Limits

Fail2Ban reacts after the log lines are written, and it works per IP. Distributed attacks from thousands of IPs and large floods need upstream help: rate limiting, caching, a CDN or WAF, and for volumetric DDoS, network-level protection. On VPS.DO, DDoS protection is available as an add-on for dedicated servers. See WAF explained and DDoS attack types.

Quick reference

Task Command
Test a filter fail2ban-regex LOG FILTER
Reload fail2ban-client reload
Jail status fail2ban-client status JAIL
Unban fail2ban-client set JAIL unbanip IP
Whitelist ignoreip in [DEFAULT]

Related: Nginx security headers and CrowdSec.

  • Nginx
  • Security
  • Fail2Ban