Document Owner: IT / Network
Scope: Linux servers running OpenSSH where Fail2Ban enforces bans and notifies n8n via webhook.
Goal: Permanently ban brute-force SSH IPs locally (bantime = -1) and send events to n8n for enrichment/alerting.
Table of Contents
- Architecture
- Prerequisites
- SOP 1 — Install Fail2Ban
- SOP 2 — Create n8n Webhook Action
- SOP 3 — Configure SSH Jail (Permanent Ban + Multi-Action)
- SOP 4 — Validate & Test
- SOP 5 — Operations (Monitoring & Health Checks)
- SOP 6 — Manual Unban
- SOP 7 — Incident Recovery (Accidental Self-Ban)
- SOP 8 — Secure the Webhook (Production Standard)
- SOP 9 — Change Control
Architecture
This design keeps Fail2Ban as the local enforcement layer and uses n8n for centralized alerting/intel.
- sshd writes auth failures to
/var/log/auth.log(or journald). - Fail2Ban detects brute-force patterns and applies a local ban.
- n8n receives webhook events to enrich (IP intel), notify (Slack), and correlate across servers.
sshd logs → Fail2Ban jail → firewall ban (%(action_)s)
↘︎ webhook notify → n8n workflow
Prerequisites
- Ubuntu/Debian server (or compatible)
- OpenSSH installed and running
- Log file exists:
/var/log/auth.log - Outbound HTTPS allowed to your n8n domain
- n8n webhook endpoint created (POST)
SOP 1 — Install Fail2Ban
Procedure
- Install packages:
sudo apt update
sudo apt install -y fail2ban
sudo systemctl enable --now fail2ban
Validation
systemctl is-active fail2ban
sudo fail2ban-client ping
SOP 2 — Create n8n Webhook Action
Create a custom Fail2Ban action that calls n8n when an IP is banned or unbanned.
Procedure
- Create the action definition:
sudo nano /etc/fail2ban/action.d/n8n-webhook.conf
Paste the following:
[Definition]
# NOTE:
# - actionban/actionunban are all we need for webhook notifications.
# - actionstart/actionstop/actioncheck are intentionally omitted.
actionban = curl -sS -m 8 -X POST "<n8n_url>" \
-H "Content-Type: application/json" \
-d '{"event":"fail2ban_ban","jail":"<name>","ip":"<ip>","fq_hostname":"<fq_hostname>","failures":"<failures>","time":"<time>","token":"<token>"}' \
>/dev/null 2>&1 || true
actionunban = curl -sS -m 8 -X POST "<n8n_url>" \
-H "Content-Type: application/json" \
-d '{"event":"fail2ban_unban","jail":"<name>","ip":"<ip>","fq_hostname":"<fq_hostname>","time":"<time>","token":"<token>"}' \
>/dev/null 2>&1 || true
[Init]
n8n_url =
token =
fq_hostname =
Notes
- Why no actionstart/stop/check? Webhooks don’t require lifecycle setup; only ban/unban events matter.
- Timeout:
-m 8prevents Fail2Ban from hanging on slow networks. - Reliability:
|| trueprevents webhook failures from breaking Fail2Ban operations.
SOP 3 — Configure SSH Jail (Permanent Ban + Multi-Action)
Procedure
- Edit the jail configuration:
sudo nano /etc/fail2ban/jail.local
Add/replace the SSH jail:
[sshd]
enabled = true
port = 22
filter = sshd
logpath = /var/log/auth.log
backend = auto
maxretry = 5
findtime = 600
bantime = -1
# Multi-action:
# 1) %(action_)s = default firewall ban action
# 2) n8n-webhook = notify n8n
action = %(action_)s
n8n-webhook[n8n_url="https://YOUR_N8N_DOMAIN/webhook/fail2ban", token="YOUR_TOKEN", fq_hostname="YOURSERVER.example.com"]
Critical Token Warning
Avoid tokens containing # unless you escape it, because # can be treated as a comment delimiter.
- Good:
SecureToken_ABC123 - If you must keep
#: escape it like\#(example:abc\#123)
Apply
sudo systemctl restart fail2ban
SOP 4 — Validate & Test
Check SSH jail status
sudo fail2ban-client status sshd
Tail Fail2Ban logs
sudo tail -n 200 /var/log/fail2ban.log
Test a ban safely
- Use a separate source IP (not your admin IP)
- Attempt several failed SSH logins to trigger
maxretry
Verify n8n
- Confirm the webhook executed in n8n
- Confirm payload includes
event,ip,fq_hostname, andjail
SOP 5 — Operations (Monitoring & Health Checks)
Daily quick checks
systemctl is-active fail2ban
sudo fail2ban-client status sshd
Investigate suspicious spikes
sudo tail -n 200 /var/log/fail2ban.log
sudo grep "Ban" /var/log/fail2ban.log | tail -n 50
Expected behavior
- Fail2Ban stays active
- Banned IPs accumulate gradually
- No repeated webhook/curl failures in logs
SOP 6 — Manual Unban
List current bans
sudo fail2ban-client status sshd
Unban a specific IP
sudo fail2ban-client set sshd unbanip 1.2.3.4
SOP 7 — Incident Recovery (Accidental Self-Ban)
Recovery steps
- Use console access (cloud console / physical / out-of-band)
- Unban your public IP:
sudo fail2ban-client set sshd unbanip YOUR.PUBLIC.IP.ADDRESS
Optional: protect stable admin IP
If your admin IP is stable, add it to ignoreip:
[sshd]
ignoreip = 127.0.0.1/8 YOUR.PUBLIC.IP.ADDRESS
SOP 8 — Secure the Webhook (Production Standard)
- Validate token in n8n as the first step
- Return 401/403 on invalid token
- Rate-limit the webhook at reverse proxy (Nginx/Traefik/Cloudflare)
- Optionally restrict source IPs to only your servers
SOP 9 — Change Control
Backup config before changes
sudo cp -a /etc/fail2ban /etc/fail2ban.bak.$(date +%F)
After changes
sudo systemctl restart fail2ban
sudo fail2ban-client status sshd
sudo tail -n 100 /var/log/fail2ban.log
Document changes: date/time, what changed, why, and expected impact.
End of SOP.
Leave a Reply