WIUNIX ACADEMY
Fail2ban for Nginx: trustworthy logs, scoped bans and recovery
Page 3 of 6. Review the earlier steps and prerequisites before continuing.
YOUR SYSTEM. YOUR STEPS.
Where are you working?
Choose your OS and version. The prerequisites, commands and troubleshooting follow your environment.
USE YOUR OWN VALUES
Configure this tutorial
Set the example values for your environment; related explanations and copy panels update together. Never enter passwords, private keys or tokens. Values stay in this tab and are not sent to the server or share links. Check actual DNS, certificates and network policy before running commands.
Different IP addresses, domains or ports? Open the settings below, change the values and select “Apply to tutorial”, then copy the updated commands.
Set values for my environment11 editable values
LET’S BUILD
Observe one authorized request without a ban
From a separate authorized lab client, open the existing protected HTTPS URL once with no credentials, using normal certificate validation. Do not brute-force it or use an account belonging to anyone else. Fail2ban is still masked. Read only the final records locally: expect ISO timestamp, f2b, the actual socket IP and 401. An already authenticated request should record its normal success status. Do not replace the hostname with a raw IP or bypass TLS verification to make this pass.
Dedicated lab host; Bash shell
sudo tail -n 8 "/var/log/nginx/academy-auth.log"
Observed address matches the direct client, with no proxy or trusted-header rewrite.
Create a dedicated filter
Use a new project-specific name instead of modifying nginx-http-auth.conf. The packaged nginx-http-auth filter targets Nginx error-log messages and is not a parser for this custom access log. Record the format/filter as one maintained contract.
Dedicated lab host; Bash shell
sudo test ! -e /etc/fail2ban/filter.d/academy-nginx-401.conf && sudo nano /etc/fail2ban/filter.d/academy-nginx-401.conf
LET’S BUILD
Anchor the address and status
Save the complete filter. Doubled percent signs are required by Fail2ban’s configuration interpolation; they are not doubled in the log. Date removal leaves the f2b marker. <HOST> parses IPv4/IPv6 and usedns=no in the jail prevents DNS lookups; no arbitrary hostname should enter this numeric log. Full-line anchors reject appended junk and non-401 statuses. Regex success alone does not prove firewall enforcement or resistance to log loss.
File contents; edit on the dedicated lab host
[Definition]
datepattern = ^%%Y-%%m-%%dT%%H:%%M:%%S%%z
failregex = ^\s*f2b <HOST> 401\s*$
ignoreregex =
A new filter exists with explicit timestamp parsing and no user-controlled header/request capture.
LET’S BUILD
Check positive, negative and IPv6 fixtures
These are synthetic documentation addresses, not traffic. Expect five matches, three misses and eight recognized timestamps. The negative trailing fake IP must not match. The fixed historical timestamps are suitable for regex parsing, not a live findtime-window test. Keep fixture data out of the real monitored log.
Dedicated lab host; Bash shell
set -e
sudo tee "/srv/academy-fail2ban-lab/fixture.log" >/dev/null <<'LOG'
2026-10-20T12:00:00+00:00 f2b 192.0.2.60 401
2026-10-20T12:00:01+00:00 f2b 192.0.2.60 401
2026-10-20T12:00:02+00:00 f2b 192.0.2.60 401
2026-10-20T12:00:03+00:00 f2b 2001:db8::60 401
2026-10-20T12:00:04+00:00 f2b 2001:db8::60 401
2026-10-20T12:00:05+00:00 f2b 192.0.2.61 200
2026-10-20T12:00:06+00:00 f2b 192.0.2.61 403
2026-10-20T12:00:07+00:00 f2b 192.0.2.61 401 injected 192.0.2.60
LOG
sudo fail2ban-regex "/srv/academy-fail2ban-lab/fixture.log" /etc/fail2ban/filter.d/academy-nginx-401.conf --usedns=no --print-all-matched --print-all-missed
Lines: 8 lines, 0 ignored, 5 matched, 3 missed
Date template hits: 8
5 matched / 3 missed; correct IPv4 and IPv6 identities; 8 date hits.
File backend and journald are separate designs
This jail deliberately uses polling against an unbuffered dedicated file. It works without python-systemd or pyinotify and handles ordinary rename/reopen log rotation, but still needs an actual rotation check on your host. backend=systemd reads journal records using journalmatch and must not use logpath. Nginx access logs are not automatically in journald just because Nginx runs under systemd. Review journal permissions, retention, rate limiting and the actual MESSAGE format before designing a journal filter. Never silently switch backends to hide an empty file.
Build a configuration with no firewall action
The isolated daemon uses only new scratch configuration, socket, PID and log files. It does not copy other jails or credentials. The audit action records validated addresses in a local text file; it invokes no nft, iptables, UFW, network request or service control. This separation matters more than merely giving a production action a test name.
Dedicated lab host; Bash shell
set -e
sudo install -d -m 0700 "/srv/academy-fail2ban-lab/conf/filter.d" "/srv/academy-fail2ban-lab/conf/action.d"
sudo cp /etc/fail2ban/filter.d/academy-nginx-401.conf "/srv/academy-fail2ban-lab/conf/filter.d/"
sudo touch "/srv/academy-fail2ban-lab/events.log"
sudo nano "/srv/academy-fail2ban-lab/conf/fail2ban.conf"