Skip to content

[!warning] Correction 2026-08-28 This article originally claimed DOSWhitelist accepts "static IPs/CIDR ranges" and told readers to put a vendor range like 192.0.64.0/18 straight into DOSWhitelist as a CIDR entry. That's wrong — DOSWhitelist has no CIDR support at all. Confirmed against the installed package's own README (libapache2-mod-evasive 1.10.1-6build1): it accepts only exact IPs or whole-octet wildcards (127.0.0.*, wildcards on up to the last 3 octets). A /N string is matched literally and will never match a real client IP.

This wasn't a hypothetical — the original wording is exactly what shipped into roles/mod_evasive on 2026-08-27, deployed to dcaprod and teelia as DOSWhitelist 192.0.64.0/18, and silently never worked: real Automattic/VaultPress IPs kept getting 403'd for a full day until caught and fixed 2026-08-28 (see Fleet-Security-Hardening). The section below is corrected accordingly.

mod_evasive Can't Whitelist Dynamic Hostnames — Bridge It to Fail2ban Instead

The Problem

mod_evasive's DOSWhitelist directive only accepts static IPs/CIDR ranges — it has no support for resolving a hostname at request time. Fail2ban's ignoreip, by contrast, does support dynamic hostnames (it re-resolves them). If a client you want to permanently exempt from flood protection has a dynamic IP (a home connection behind a dynamic-DNS name, for example), DOSWhitelist alone can't keep up — the IP drifts, the whitelist entry goes stale, and the client starts getting legitimately flagged as a flood source.

This bit a real deploy: a home IP was hardcoded into mod_evasive_whitelist on 2026-08-19. By the time an unrelated fix landed a week later, the home connection had renumbered and the static entry was already wrong.

The Fix — a live-lookup bridge, not a bigger whitelist

Don't try to keep the static list current. Instead:

  1. Keep DOSWhitelist for genuinely static exemptions (localhost, known-static vendor ranges like Automattic/Jetpack's for VaultPress/Jetpack backups). DOSWhitelist has no CIDR support — a range like 192.0.64.0/18 must be expanded into one whole-octet-wildcard line per covered value (e.g. 192.0.64.* through 192.0.127.* for that /18 — 64 lines). The Tailscale range (100.64.0.0/10 in roles/mod_evasive/defaults/main.yml) has this same unfixed problem fleet-wide as of 2026-08-28 — see Fleet-Security-Hardening.
  2. For anything with a dynamic IP, have mod_evasive's notify script do a live getent hosts <hostname> lookup against the blocked IP before deciding to escalate. If the blocked IP currently matches the dynamic hostname's live resolution, skip logging it to the bridge file — which means fail2ban (watching that bridge file) never sees it and never bans it.
  3. Everything else gets written to the bridge log and picked up by a fail2ban jail/filter pair watching that file, giving you fail2ban's IP-ban enforcement on top of mod_evasive's flood detection, with fail2ban's ignoreip (which does re-resolve dynamic hostnames on each check) as the final safety net.

This means the dynamic IP only needs to be correctly resolvable right now at the moment of each block decision — no stale static entry to maintain.

The permission trap this pattern walks into

The obvious place to put the bridge log is next to Apache's other logs, /var/log/apache2/. Don't — on a hardened install this directory is root:adm 0750, and the www-data user Apache/mod_evasive runs as has zero traverse permission into it, regardless of the target file's own ownership. Writing there fails silently in a way that's easy to misdiagnose as a mod_evasive bug rather than a directory permission issue.

Put the bridge log inside mod_evasive's own log directory instead (typically /var/log/mod_evasive/, already www-data:www-data 0750 since mod_evasive itself needs to write there) — no permission changes needed anywhere else, and no reason to carve an exception into Apache's hardened log directory.

Verifying it actually works

  • Confirm the static entries in DOSWhitelist with httpd -M | grep evasive / checking the loaded config, not just the source file.
  • Trigger a synthetic flood from the dynamic-hostname client and confirm no bridge-log entry appears (proves the live lookup + skip logic fired).
  • Trigger a synthetic flood from an IP that does not match the dynamic hostname and confirm it does appear in the bridge log and gets picked up by the fail2ban jail.
  • Watch for the home IP actually changing over a real DHCP renewal cycle and confirm the client is still exempted with zero manual intervention — this is the actual point of the pattern, and a one-time test won't prove it.
  • fail2ban-digest-mode-fleet — general fail2ban fleet conventions this pattern sits alongside.
  • Vendor-range whitelisting (Automattic/Jetpack, for VaultPress backups) is a static exemption and belongs in DOSWhitelist directly — don't route it through the dynamic-lookup bridge, that's unnecessary overhead for an IP range that never changes. Remember to expand it into whole-octet wildcards, not a CIDR string.