Documents the gotcha discovered during the 2026-04-30 DCAProd XML-RPC
outage triage: wp-fail2ban plugin emits via PHP syslog(LOG_AUTH) which
lands in /var/log/auth.log on Debian/Ubuntu, not /var/log/syslog.
wordpress-{hard,soft,extra} jails configured with logpath=/var/log/syslog
(common in tutorials and ansible roles) silently catch zero events.
Article includes diagnostic steps, the fix, fail2ban-regex verification,
distro cheat sheet (Debian/Ubuntu vs RHEL/Fedora vs systemd-journal-only),
and a note on why wordpress-login is unaffected.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
151 lines
5.2 KiB
Markdown
151 lines
5.2 KiB
Markdown
---
|
|
title: "wp-fail2ban Plugin Logpath on Debian/Ubuntu (auth.log, not syslog)"
|
|
domain: selfhosting
|
|
category: security
|
|
tags: [fail2ban, wordpress, wp-fail2ban, debugging, gotcha, debian, ubuntu]
|
|
status: published
|
|
created: 2026-04-30
|
|
updated: 2026-04-30
|
|
---
|
|
# wp-fail2ban Plugin Logpath on Debian/Ubuntu (auth.log, not syslog)
|
|
|
|
## The Problem
|
|
|
|
You install the [WP fail2ban](https://wordpress.org/plugins/wp-fail2ban/) WordPress plugin, configure the fleet-standard `wordpress-hard`, `wordpress-soft`, and `wordpress-extra` jails, and… nothing. Weeks pass. `fail2ban-client status wordpress-hard` reports `Total failed: 0, Total banned: 0`. Your site is being attacked, but the jails are dead.
|
|
|
|
Meanwhile the `wordpress-login` jail (which reads Apache access logs for `POST /wp-login.php` directly) is happily catching brute-forcers. So the problem isn't fail2ban itself — it's specifically the wp-fail2ban-plugin-derived jails.
|
|
|
|
## The Cause
|
|
|
|
The wp-fail2ban plugin emits events via PHP's `syslog()` call with facility `LOG_AUTH`. On Debian/Ubuntu, rsyslog routes the `auth` facility to **`/var/log/auth.log`**, NOT `/var/log/syslog`. On RHEL/Fedora it's `/var/log/secure`.
|
|
|
|
A lot of tutorials, ansible-galaxy roles, and copy-paste config snippets specify:
|
|
|
|
```ini
|
|
logpath = /var/log/syslog
|
|
```
|
|
|
|
That's wrong on Debian/Ubuntu. The events never land there, so the filter regex has nothing to match, so the jail catches zero events forever. Silently.
|
|
|
|
## Diagnostic Steps
|
|
|
|
If a `wordpress-{hard,soft,extra}` jail shows `Total failed: 0` over a long window despite the plugin being active and the site getting attacked:
|
|
|
|
**1. Check what the jail thinks it's watching:**
|
|
|
|
```bash
|
|
sudo fail2ban-client status wordpress-hard | grep "File list"
|
|
```
|
|
|
|
**2. Check where wp-fail2ban events actually land:**
|
|
|
|
```bash
|
|
sudo grep -c "wordpress(" /var/log/auth.log /var/log/syslog /var/log/secure 2>/dev/null
|
|
```
|
|
|
|
You'll see something like:
|
|
|
|
```
|
|
/var/log/auth.log:314
|
|
/var/log/syslog:0
|
|
```
|
|
|
|
**3. If the jail's `File list` ≠ the file with events, fix the `logpath`.**
|
|
|
|
A real event line on Debian/Ubuntu looks like:
|
|
|
|
```
|
|
2026-04-18T23:28:21.027004-04:00 hostname wordpress(example.com)[719989]: XML-RPC authentication failure for someone from 1.2.3.4
|
|
```
|
|
|
|
The `wordpress(domain)[pid]` syslog tag is the giveaway — those are wp-fail2ban events.
|
|
|
|
## The Fix
|
|
|
|
Edit the jail blocks in `/etc/fail2ban/jail.local` (or your Ansible source for the jail) and set:
|
|
|
|
```ini
|
|
[wordpress-hard]
|
|
enabled = true
|
|
port = http,https
|
|
filter = wordpress-hard
|
|
logpath = /var/log/auth.log
|
|
maxretry = 1
|
|
findtime = 60
|
|
bantime = 30d
|
|
backend = polling
|
|
|
|
[wordpress-soft]
|
|
enabled = true
|
|
port = http,https
|
|
filter = wordpress-soft
|
|
logpath = /var/log/auth.log
|
|
maxretry = 5
|
|
findtime = 60
|
|
bantime = 30d
|
|
backend = polling
|
|
|
|
[wordpress-extra]
|
|
enabled = true
|
|
port = http,https
|
|
filter = wordpress-extra
|
|
logpath = /var/log/auth.log
|
|
maxretry = 5
|
|
findtime = 60
|
|
bantime = 30d
|
|
backend = polling
|
|
```
|
|
|
|
Then:
|
|
|
|
```bash
|
|
sudo fail2ban-client -t # validate
|
|
sudo fail2ban-client reload
|
|
sudo fail2ban-client status wordpress-hard | grep "File list"
|
|
# should now show /var/log/auth.log
|
|
```
|
|
|
|
## Verification
|
|
|
|
You can prove the filter regex actually matches your real events without waiting for an attack — run `fail2ban-regex` against the rotated log:
|
|
|
|
```bash
|
|
sudo fail2ban-regex /var/log/auth.log.1 /etc/fail2ban/filter.d/wordpress-hard.conf | grep -E "Failregex:|Lines:"
|
|
```
|
|
|
|
Healthy output looks like:
|
|
|
|
```
|
|
Failregex: 81 total
|
|
Lines: 13008 lines, 0 ignored, 81 matched, 12927 missed
|
|
```
|
|
|
|
If you see `Failregex: 0 total`, the filter regex doesn't match what the plugin actually emits — which is a different bug (filter version skew vs. plugin version), not the logpath gotcha. Investigate `/etc/fail2ban/filter.d/wordpress-{hard,soft}.conf` against actual event lines.
|
|
|
|
> **Note:** On a freshly-fixed jail, counters will sit at `Total failed: 0` for a while — the `polling` backend starts at the file's current EOF, so old events aren't retroactively counted. New events from the moment of `reload` onward will accumulate. Allow a few days of normal attack traffic before declaring the fix broken.
|
|
|
|
## Distribution Cheat Sheet
|
|
|
|
| Distro family | wp-fail2ban events land in |
|
|
|---|---|
|
|
| Debian / Ubuntu | `/var/log/auth.log` |
|
|
| RHEL / CentOS / Fedora | `/var/log/secure` |
|
|
| systemd-journal-only systems | `journalctl SYSLOG_FACILITY=4` (use `backend = systemd` + `journalmatch = SYSLOG_FACILITY=4`) |
|
|
|
|
If you have a mixed fleet, parameterize the path:
|
|
|
|
```yaml
|
|
# Ansible vars
|
|
wp_fail2ban_log_path: "{{ '/var/log/auth.log' if ansible_os_family == 'Debian' else '/var/log/secure' }}"
|
|
```
|
|
|
|
## Why wordpress-login Is Unaffected
|
|
|
|
The `wordpress-login` jail is a different beast — it reads `/var/log/apache2/access.log` directly and matches `^<HOST> -.*"POST /wp-login.php` via the `wordpress-login` filter. No plugin involved, no syslog facility involved. So a host can have `wordpress-login` working perfectly while `wordpress-{hard,soft,extra}` are silently dead. Don't let a healthy `wordpress-login` reassure you that the rest of the wp-fail2ban stack is also fine.
|
|
|
|
## Related
|
|
|
|
- [[fail2ban-wordpress-login-jail]] — the access-log layer that catches WP brute force without any plugin dependency
|
|
- [[fail2ban-apache-bad-request-jail]]
|
|
- [[fail2ban-apache-php-probe-jail]]
|
|
- [[clamav-fleet-deployment]]
|