← Start of tutorial

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.

Ubuntu 26.04 LTSReviewed against official documentation ·

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
11

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.

INI · /srv/academy-fail2ban-lab/conf/fail2ban.conf

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
How do I know it worked?

Only scratch paths are referenced; no production socket or database.

Open the harmless action file

Create the following action only inside the scratch configuration.

Bash

Dedicated lab host; Bash shell

sudo nano "/srv/academy-fail2ban-lab/conf/action.d/academy-audit.conf"
12

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.

INI · /srv/academy-fail2ban-lab/conf/action.d/academy-audit.conf

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'
How do I know it worked?

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.

Bash

Dedicated lab host; Bash shell

sudo nano "/srv/academy-fail2ban-lab/conf/jail.conf"
13

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.

INI · /srv/academy-fail2ban-lab/conf/jail.conf

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
How do I know it worked?

One scratch-only jail uses the audit action and the reviewed thresholds.

14

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.

Bash

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"
How do I know it worked?

Exactly the two documentation addresses appear in the ban list/audit; management and success records do not.

Explore all tutorials ↗