# fail2ban

Filters, jails, and supporting Apache config developed while responding to specific attacks, kept here for reuse on other hosts. Not every file assumes the same environment — this table says which ones do.

## Applicability

A note on ISPConfig. ISPConfig creates individual web logs for each site, limiting the logs the owner of that site may view to the logs pertaining to that site only. The combined logs of all sites do not exist (/var/log/apache2/access.log only shows access requests which are not a part of a particular vhost).

To use fail2ban in this situation would require that fail2ban monitor many log directories, one for each vhost. I chose to create a combined log that contains all log entries from all vhosts. This duplicates the indiviual vhost logs, but allows fail2ban to track all activity across all sites, detecting and blocking attacks. See apache-notes/apache-logformat.md for additional information. The column "Requires ISPConfig?" in the following table actually means "designed for a system described in apache-notes/apache-logformat.md".

| File | Requires ISPConfig? | Notes |
|---|---|---|
| `filter.d/apache-vhost-wp-probe.conf` | No | Pure regex against a log line; no panel dependency at all. Renamed from `apache-wp-probe.conf` 2026-09-15 — see "Naming convention" below. |
| `filter.d/apache-vhost-dokuwiki-crawl.conf` | No | Same; renamed from `apache-dokuwiki-crawl.conf`. |
| `jail.d/apache-vhost-wp-probe.local` | No | `jail.d/*.local` is a safe placement choice on any host, panel-managed or not — see the file's own comment for why. Renamed from `wp-probe.local` 2026-09-15 (first to add the `apache-` prefix, then again the same day to insert `-vhost-`). |
| `jail.d/apache-vhost-dokuwiki-crawl.local` | No | Same; renamed from `dokuwiki-crawl.local`. |
| `filter.d/apache-vhost-badbots.conf` | No | New 2026-09-15. Vhost-log-aware rewrite of the stock `apache-badbots` filter (known bad-bot/scraper User-Agent signatures) — the stock filter can't be used as-is against a shared multi-vhost log, see `apache-notes/apache-logformat.md` "Why append rather than restructure". Reuses the stock filter's bot-signature list via `[INCLUDES] before=` instead of copying it. Verified with `fail2ban-regex` against synthetic vhost-format lines. |
| `jail.d/apache-vhost-badbots.local` | No | Pairs with the filter above. `maxretry = 1`, matching the stock jail — a signature hit is already high-confidence. |
| `filter.d/apache-vhost-fakegooglebot.conf` | No | New 2026-09-15. Vhost-log-aware rewrite of the stock `apache-fakegooglebot` filter (UA claims `Googlebot/` but may not be Google). Verified with `fail2ban-regex`. |
| `jail.d/apache-vhost-fakegooglebot.local` | No | Pairs with the filter above. Requires the `ignorecommand` it sets — that's what actually distinguishes real Googlebot (reverse-DNS confirmed) from a spoofed UA; without it this jail would ban real Googlebot too. The script it calls ships with the fail2ban package itself. |
| `filter.d/apache-vhost-scan-burst.conf` | No | New 2026-09-15. Generic "many 4xx responses from one IP in a short window" detector, built from `testing/allegra.log` (a ~210-request-in-2-seconds secrets/vuln scan against `atelierallegra.com` using a rotating fake-AI-crawler User-Agent per request). Behavioral, not signature-based — counts status codes, not paths or UAs — so it isn't tied to any one scanner's target list. Excludes `401` from the count (auth challenge is a different pattern than "throw everything, see what isn't a 404"). Verified with `fail2ban-regex` against synthetic vhost-format lines including a 401 and a 200 line to confirm both are correctly ignored. |
| `jail.d/apache-vhost-scan-burst.local` | No | Pairs with the filter above. `maxretry = 18`, `findtime = 45s` — the middle of the user-confirmed 15-20 retries / 30-60s window; tune within that range per host. |
| `action.d/blacklistip.conf` | No | Not a dynamic ban action — loads a static iptables chain from a flat IP/subnet list at fail2ban start, tears it down at stop. Ran in production for roughly 10 years; currently silently not blocking after a host upgrade — open investigation, see `WorkingNotes/Backlog.md`. Must be named to match the `action =` line in `jail.d/blacklist.local` exactly. |
| `filter.d/blacklistip.conf` | No | Intentionally empty (`failregex` unset) — this jail never reads a log; it exists only so fail2ban's start/stop lifecycle triggers the action above. |
| `jail.d/blacklist.local` | No | `filename=` points at a flat file, one IP/subnet per line, no comments, no blank lines. `bantime = -1` (permanent) is correct since there is no dynamic unban. Requires `logpath = /dev/null` and `backend = polling` since no log is ever actually read — omitting them can prevent the jail from starting at all. |
| `apache-notes/dokuwiki-block.md` | No, but the file assumes *some* answer to "how do I add an Apache directive here" | Option 1 (`.htaccess`) works everywhere. Option 2 names ISPConfig3's panel field as one example of a vhost-level directives mechanism; on a hand-managed host, use the vhost/conf file's own `<Directory>`/`<VirtualHost>` block instead. |
| `apache-notes/apache-logformat.md` | No | The specific finding recorded in it (per-site logs already stock `combined`, no overrides) is Devaun-specific evidence, re-check on a new host with the `grep` it gives you. The recommendation itself (append fields, don't restructure) is general reasoning that will usually still apply, but re-verify against that host's stock filters before assuming it. |
| `conf-available/fail2ban-log.conf` | No | The `conf-available` drop-in that `apache-logformat.md` tells you to deploy; not meant to be read as prose. Canonical copy as of 2026-09-15 — moved out of `apache-notes/` into its own directory so it mirrors `/etc/apache2/conf-available/` the same way `filter.d/`/`jail.d/`/`action.d/` mirror fail2ban's layout. A duplicate still sits at `apache-notes/fail2ban-log.conf`; that copy is stale and safe to remove. |
| `Documentation/MitigationPlan.md` | Yes | Same — describes what was actually deployed where, including ISPConfig panel steps specific to that host. |
| `Documentation/dead-ends/apache-errorlogformat.md`, `Documentation/dead-ends/fail2ban-error-log.conf` | No | Not deployable — moved here 2026-09-15 from `apache-notes/`/`conf-available/` once superseded by `apache-vhost-scan-burst`. Read the "Status" section at the top of the `.md` before considering reviving this. |
| `scripts/getFail2BanJailStats` | No | Talks only to `fail2ban-client`; has no opinion on what manages Apache. |
| `scripts/setWebServer` | No | Deployment script: installs `conf-available/fail2ban-log.conf`, disables the stock `other-vhosts-access-log` conf, enables the fail2ban one, and copies every `apache-vhost-*` filter/jail across in one pass via glob (updated 2026-09-15 to do this, replacing an earlier hard-coded single-jail version). Still only checks the status of one jail (`apache-vhost-wp-probe`) at the end, which is cosmetic, not a functional gap. |

## Directory layout

- `filter.d/`, `jail.d/`, `action.d/`, `conf-available/` — mirror fail2ban's and Apache's own config layouts, so a file's destination is its path here minus the leading directory. `conf-available/` was added 2026-09-15 specifically so Apache-side drop-ins get the same direct-`cp` treatment as the fail2ban-side ones, instead of living under `apache-notes/` as prose-adjacent files.
- `apache-notes/` — Apache-side write-ups that pair with a jail: markdown docs, plus any Apache directives that don't have a `conf-available` home of their own (e.g. `dokuwiki-block.md`'s snippet). Deployable drop-ins now live in `conf-available/` instead.
- `scripts/` — standalone tools: `getFail2BanJailStats` (reports jail stats via `fail2ban-client`) and `setWebServer` (installs the Apache/fail2ban config for a jail in one pass — see the applicability table above for its current limitations).
- `testing/` — captured log samples used to `fail2ban-regex`-verify the filters above. Not attack traffic to run against a live host; kept as regression fixtures. Nor saved in subversion
- `Documentation/` — incident write-ups. These describe one specific engagement (Devaun Daedalus / ISPConfig3) and should be read as history, not generalized without checking the assumptions section in each one. `Documentation/dead-ends/` holds approaches that were built and then abandoned — kept for the reasoning and evidence, explicitly not meant to be deployed; each file there starts with a "Status" section explaining why.
- `WorkingNotes/` - Place for notes and other short term files. Not saved in subversion

## General principle for new filters and jails added here

`filter.d/` and `jail.d/` entries should stay panel-agnostic — they operate on a log file and fail2ban's own config, neither of which cares what's running Apache underneath. `apache-notes/` entries inherently need a "how do I add this to Apache" step, which does vary by host; call out ISPConfig-specific steps as one option among others (vhost-level edit, other panels) rather than the only path, the way `dokuwiki-block.md` does.

## Naming convention

Every filter and jail that targets an Apache log is named `apache-<thing>.conf` / `apache-<thing>.local`, in both `filter.d/` and `jail.d/` — e.g. `apache-vhost-wp-probe.conf` + `apache-vhost-wp-probe.local`. This was made consistent 2026-09-15 (jail files had lagged the filter files, using bare `wp-probe.local`/`dokuwiki-crawl.local`) specifically so a deployment script can select every Apache-related file with a single glob, e.g. `cp filter.d/apache* /etc/fail2ban/filter.d/`, rather than listing files by name one at a time. Keep this prefix on any new Apache filter/jail pair added here.

Within that, `apache-vhost-<thing>` (note the second segment) marks a filter/jail written specifically against the shared multi-vhost `fail2ban` LogFormat described in `apache-notes/apache-logformat.md` — vhost field present, fields appended after the User-Agent, `other_vhosts_access.log` as the `logpath`. This distinguishes them from any future `apache-<thing>` filter/jail built against a plain single-vhost stock log instead (see the deferred stock-`combined` variants in `WorkingNotes/Backlog.md`), which would use the bare `apache-` prefix with no `-vhost-` segment, matching how the equivalent stock fail2ban filters (`apache-badbots`, etc.) are themselves named. Applied 2026-09-15 to `apache-wp-probe`/`apache-dokuwiki-crawl` (renamed to `apache-vhost-wp-probe`/`apache-vhost-dokuwiki-crawl`) and to three new filter/jail pairs: `apache-vhost-badbots`, `apache-vhost-fakegooglebot`, and `apache-vhost-scan-burst` (generic "many 4xx from one IP in a short window" burst detector — see `Documentation/dead-ends/apache-errorlogformat.md` for why this replaced the originally-planned combined-error-log approach for cross-site scan detection). Rewriting the remaining stock error-log jails (`apache-auth`, `apache-noscript`, etc.) as `apache-vhost-*` was the original plan for closing the same `ErrorLog`-per-vhost gap, but that path was abandoned 2026-09-15 — see `Documentation/dead-ends/` before picking it back up.
