majorwiki/02-selfhosting/security/wp-fail2ban-logpath-debian-ubuntu.md
MajorLinux 9e96ebb110 wiki: add wp-fail2ban logpath on Debian/Ubuntu (auth.log not syslog)
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>
2026-04-30 05:21:50 -04:00

5.2 KiB

title domain category tags status created updated
wp-fail2ban Plugin Logpath on Debian/Ubuntu (auth.log, not syslog) selfhosting security
fail2ban
wordpress
wp-fail2ban
debugging
gotcha
debian
ubuntu
published 2026-04-30 2026-04-30

wp-fail2ban Plugin Logpath on Debian/Ubuntu (auth.log, not syslog)

The Problem

You install the 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:

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:

sudo fail2ban-client status wordpress-hard | grep "File list"

2. Check where wp-fail2ban events actually land:

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:

[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:

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:

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:

# 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.