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.