Spent days wondering why fail2ban kept banning IPs that kept hammering my Docker-published WordPress login. The bans were inserted, fail2ban-client status said "banned" — but the traffic never stopped.
The reason: on a Docker host, fail2ban's default action drops rules into the INPUT chain. Packets to published ports are processed by Docker's FORWARD/DOCKER-USER chain before INPUT ever sees them. So the ban is real, it just drops nothing.
The fix is one token — point the action at DOCKER-USER:
[igornews-wp] enabled = true filter = igornews-wp logpath = /opt/igornews/logs/access.log action = iptables[type=allports, chain=DOCKER-USER, blocktype=DROP] bantime = 1h findtime = 10m maxretry = 6 Second gotcha in the same jail: it reads Caddy's JSON access log, whose timestamp is a fractional epoch mid-line, so I needed a custom datepattern = "ts":{LEPOCH} or fail2ban parsed zero lines.
The verify step that would've caught this in five minutes: after a ban, run iptables -L DOCKER-USER -n and actually see the IP there — never trust "fail2ban is running."
Full walk-through (with the JSON-log filter regex): https://igornews.com/2026/11/08/fail2ban-docker-user-chain-bans/
