Fail2ban: Enable the nginx-bad-request Jail¶
The Problem¶
Automated scanners sometimes send malformed HTTP requests — empty request lines, truncated headers, or garbage data — that nginx rejects with a 400 Bad Request. These aren't caught by the default fail2ban jails (nginx-botsearch, nginx-http-auth) because those target URL-probe patterns and auth failures, not raw protocol abuse.
In a real incident: a single IP (185.177.72.70) sent 2,778 malformed requests in ~4 minutes, driving Netdata's web_log_1m_bad_requests to 93.7% and triggering a CRITICAL alert. The neighboring IP (185.177.72.61) was already banned — the /24 was known-bad and operating in shifts.
The Solution¶
fail2ban ships a nginx-bad-request filter out of the box. It's just not wired to a jail by default. Enabling it is a one-step drop-in.
Step 1 — Create the jail drop-in¶
Create /etc/fail2ban/jail.d/nginx-bad-request.conf:
[nginx-bad-request]
enabled = true
port = http,https
filter = nginx-bad-request
logpath = /var/log/nginx/access.log
maxretry = 10
findtime = 60
bantime = 1h
Settings rationale:
- maxretry = 10 — a legitimate browser never sends 10 malformed requests; this threshold catches burst scanners immediately
- findtime = 60 — 60-second window; the attack pattern fires dozens of requests per minute
- bantime = 1h — reasonable starting point; pair with recidive for repeat offenders
Step 2 — Verify the filter matches your log format¶
Before reloading, confirm the stock filter matches your nginx logs:
In a real-world test against an active server this matched 2,829 lines with zero false positives.
Step 3 — Reload fail2ban¶
You can also ban an IP manually while the jail is loading:
Verify It's Working¶
# Check jail status and active bans
fail2ban-client status nginx-bad-request
# Watch bans in real time
tail -f /var/log/fail2ban.log | grep nginx-bad-request
# Confirm the jail is monitoring the right file
fail2ban-client get nginx-bad-request logpath
Key Notes¶
- The stock filter is at
/etc/fail2ban/filter.d/nginx-bad-request.conf— no need to create it. - If your
[DEFAULT]section setsbackend = systemd(common on Fedora/RHEL), addbackend = pollingto the jail or it will silently ignorelogpathand monitor journald instead — where nginx doesn't write. - Make sure your Tailscale subnet (
100.64.0.0/10) is inignoreipunder[DEFAULT]to avoid banning your own monitoring. - This jail targets 400 Bad Request responses. For 404 scanner detection, see fail2ban-apache-404-scanner-jail.
See Also¶
- fail2ban-apache-bad-request-jail — Apache equivalent (no stock filter; custom filter required)
- fail2ban-apache-404-scanner-jail
- fail2ban-apache-php-probe-jail