All sourced from 2026-04-17 work sessions: - fail2ban-nginx-bad-request-jail: enable stock jail (just needs wiring) - fail2ban-apache-bad-request-jail: custom filter from scratch, no stock equivalent - ssh-hardening-ansible-fleet: drop-in approach with Fedora/Ubuntu edge cases - watchtower-smtp-localhost-relay: credential-free localhost postfix relay pattern Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
4.2 KiB
title, domain, category, tags, status, created, updated
| title | domain | category | tags | status | created | updated | |||||
|---|---|---|---|---|---|---|---|---|---|---|---|
| Fail2ban Custom Jail: Apache Bad Request Detection | selfhosting | security |
|
published | 2026-04-17 | 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:
# 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 = ^<HOST> -.*".*" 400 \d+
ignoreregex =
datepattern = %%d/%%b/%%Y:%%H:%%M:%%S %%z
Step 2 — Validate the filter
Always test before deploying:
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:
[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]setsbackend = systemd, addbackend = pollingto the jail — otherwise it silently ignoreslogpathand reads journald instead.
Step 4 — Reload fail2ban
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:
- 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
# 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 — the nginx equivalent (stock filter, just needs wiring)
- fail2ban-apache-404-scanner-jail — catches 404 probe scanners
- fail2ban-apache-php-probe-jail