WIUNIX ACADEMY
Fail2ban for Nginx: trustworthy logs, scoped bans and recovery
Page 4 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
Keep all daemon state inside the lab
Save this complete minimal global configuration. No persistent ban database is used. This daemon is not registered with systemd and is reached only through its dedicated socket.
File contents; edit on the dedicated lab host
[Definition]
loglevel = INFO
logtarget = /srv/academy-fail2ban-lab/daemon.log
socket = /srv/academy-fail2ban-lab/fail2ban.sock
pidfile = /srv/academy-fail2ban-lab/fail2ban.pid
dbfile = None
Only scratch paths are referenced; no production socket or database.
Open the harmless action file
Create the following action only inside the scratch configuration.
Dedicated lab host; Bash shell
sudo nano "/srv/academy-fail2ban-lab/conf/action.d/academy-audit.conf"
LET’S BUILD
Record ban and unban decisions only
There is deliberately no actionstart, actionstop or actioncheck command. Percent escaping is for INI interpolation. Only Fail2ban-validated IP tags enter these quoted strings; do not add request-derived tags or shell fragments.
File contents; edit on the dedicated lab host
[Definition]
actionban = printf '%%s\n' 'BAN <ip>' >> '/srv/academy-fail2ban-lab/actions.log'
actionunban = printf '%%s\n' 'UNBAN <ip>' >> '/srv/academy-fail2ban-lab/actions.log'
The only side effect is appending a local audit line.
Open the isolated jail file
This file has one enabled jail and no include of package jails.
Dedicated lab host; Bash shell
sudo nano "/srv/academy-fail2ban-lab/conf/jail.conf"
LET’S BUILD
Set the window, threshold and exact allowlist
Save the whole file. maxretry=5 counts failures per address within findtime=600 seconds. At the threshold, a ban lasts 600 seconds. A single source behind NAT may represent many legitimate users. Choose conservative values after observing normal challenges; there is no universally correct threshold. Loopback and the exact management IPv4 are ignored. The lab uses documentation IPs not included in this list.
File contents; edit on the dedicated lab host
[academy-nginx-lab]
enabled = true
filter = academy-nginx-401
backend = polling
logpath = /srv/academy-fail2ban-lab/events.log
usedns = no
ignoreip = 127.0.0.1/8 ::1 198.51.100.10
findtime = 600
maxretry = 5
bantime = 600
action = academy-audit
One scratch-only jail uses the audit action and the reviewed thresholds.
LET’S BUILD
Exercise decisions without changing a firewall
Start only the scratch daemon. Current timestamps exercise the real window, unlike the static parser fixture. It should record documentation IPv4/IPv6 bans, but never 198.51.100.10 or the 200-response address. Polling is asynchronous; if three seconds is insufficient, inspect status again rather than adding more failures. No real traffic is generated and these decisions do not establish internet protection.
Dedicated lab host; Bash shell
set -e
sudo fail2ban-client -c "/srv/academy-fail2ban-lab/conf" -t
sudo fail2ban-client -c "/srv/academy-fail2ban-lab/conf" -s "/srv/academy-fail2ban-lab/fail2ban.sock" -p "/srv/academy-fail2ban-lab/fail2ban.pid" start
sudo python3 - <<'PY'
from datetime import datetime
from pathlib import Path
stamp = datetime.now().astimezone().isoformat(timespec='seconds')
with Path('/srv/academy-fail2ban-lab/events.log').open('a') as f:
for address in ('192.0.2.60', '2001:db8::60', '198.51.100.10'):
for _ in range(5):
f.write(f'{stamp} f2b {address} 401\n')
f.write(f'{stamp} f2b 192.0.2.61 200\n')
PY
sleep 3
sudo fail2ban-client -c "/srv/academy-fail2ban-lab/conf" -s "/srv/academy-fail2ban-lab/fail2ban.sock" status academy-nginx-lab
sudo cat "/srv/academy-fail2ban-lab/actions.log"
Exactly the two documentation addresses appear in the ban list/audit; management and success records do not.