--- title: "Fail2ban Custom Jail: Apache Bad Request Detection" domain: selfhosting category: security tags: [fail2ban, apache, security, firewall, bad-request] status: published created: 2026-04-17 updated: 2026-04-17 --- # Fail2ban Custom Jail: Apache Bad Request Detection ## The Problem fail2ban ships a stock `nginx-bad-request` filter for catching malformed HTTP requests (400s), but **there is no Apache equivalent**. Apache servers are left unprotected against the same class of attack: scanners that send garbage request lines to probe for vulnerabilities or overwhelm the access log. Unlike the nginx version, this filter has to be written from scratch. ## The Solution Create a custom filter targeting **400 Bad Request** responses in Apache's Combined Log Format, then wire it to a jail. ### Step 1 — Create the filter Create `/etc/fail2ban/filter.d/apache-bad-request.conf`: ```ini # Fail2Ban filter: catch 400 Bad Request responses in Apache access logs # Targets malformed HTTP requests — garbage request lines, empty methods, etc. # No stock equivalent exists; nginx-bad-request ships with fail2ban but Apache does not. [Definition] # Match 400 responses in Apache Combined/Common Log Format failregex = ^ -.*".*" 400 \d+ ignoreregex = datepattern = %%d/%%b/%%Y:%%H:%%M:%%S %%z ``` ### Step 2 — Validate the filter Always test before deploying: ```bash fail2ban-regex /var/log/apache2/access.log /etc/fail2ban/filter.d/apache-bad-request.conf ``` Against a live server under typical traffic this matched **155 lines with zero false positives**. If you see unexpected matches, refine `ignoreregex`. ### Step 3 — Create the jail drop-in Create `/etc/fail2ban/jail.d/apache-bad-request.conf`: ```ini [apache-bad-request] enabled = true port = http,https filter = apache-bad-request logpath = /var/log/apache2/access.log maxretry = 10 findtime = 60 bantime = 1h ``` > **Note:** On Fedora/RHEL, the log path may be `/var/log/httpd/access_log`. If your `[DEFAULT]` sets `backend = systemd`, add `backend = polling` to the jail — otherwise it silently ignores `logpath` and reads journald instead. ### Step 4 — Reload fail2ban ```bash systemctl reload fail2ban fail2ban-client status apache-bad-request ``` ## Deploy Fleet-Wide with Ansible If you run multiple Apache hosts, use Ansible to deploy both the filter and jail atomically: ```yaml - name: Deploy apache-bad-request fail2ban filter ansible.builtin.template: src: templates/fail2ban_apache_bad_request_filter.conf.j2 dest: /etc/fail2ban/filter.d/apache-bad-request.conf notify: Reload fail2ban - name: Deploy apache-bad-request fail2ban jail ansible.builtin.template: src: templates/fail2ban_apache_bad_request_jail.conf.j2 dest: /etc/fail2ban/jail.d/apache-bad-request.conf notify: Reload fail2ban ``` ## Why Not Use nginx-bad-request on Apache? The `nginx-bad-request` filter parses nginx's log format, which differs from Apache's Combined Log Format. The timestamp format, field ordering, and quoting differ enough that the regex won't match. You need a separate filter. | | nginx-bad-request | apache-bad-request | |---|---|---| | Ships with fail2ban | ✅ Yes | ❌ No — must write custom | | Log source | nginx access log | Apache access log | | What it catches | 400 responses (malformed requests) | 400 responses (malformed requests) | | Regex target | nginx Combined Log Format | Apache Combined Log Format | ## Diagnostic Commands ```bash # Validate filter against live log fail2ban-regex /var/log/apache2/access.log /etc/fail2ban/filter.d/apache-bad-request.conf # Check jail status fail2ban-client status apache-bad-request # Confirm the jail is monitoring the correct log file fail2ban-client get apache-bad-request logpath # Watch bans in real time tail -f /var/log/fail2ban.log | grep apache-bad-request # Count 400s in today's access log grep '" 400 ' /var/log/apache2/access.log | wc -l ``` ## See Also - [fail2ban-nginx-bad-request-jail](fail2ban-nginx-bad-request-jail.md) — the nginx equivalent (stock filter, just needs wiring) - [fail2ban-apache-404-scanner-jail](fail2ban-apache-404-scanner-jail.md) — catches 404 probe scanners - [fail2ban-apache-php-probe-jail](fail2ban-apache-php-probe-jail.md)