Files
MajorWiki/02-selfhosting/security/fail2ban-apache-bad-request-jail.md
MajorLinux 961ce75b88 Add 4 articles: nginx/apache bad-request jails, SSH fleet hardening, Watchtower localhost relay
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>
2026-04-17 21:06:09 -04:00

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
fail2ban
apache
security
firewall
bad-request
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] sets backend = systemd, add backend = polling to the jail — otherwise it silently ignores logpath and 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