Automatic intrusion prevention for Linux servers monitors logs, detects brute-force attacks, and blocks offending IPs with a firewall rule, no manual intervention required.
17 Mar 202616 min read6
fail2ban — Intrusion Prevention Reference
Automatic intrusion prevention for Linux servers — monitors logs, detects brute-force attacks, and blocks offending IPs with a firewall rule, no manual intervention required.
fail2ban is a daemon that watches log files for repeated authentication failures and other suspicious patterns, then fires a configurable action — most commonly an iptables / nftables ban — against the offending IP address for a set duration.
fail2ban turns your existing log files into an always-on intrusion prevention layer, with zero changes to the services it protects.
It works through two concepts: filters (regex rules that match bad log lines) and jails (policies that bind a filter to a log file and define thresholds). Hundreds of filters ship out of the box; writing your own takes minutes.
Component
Role
Filter
A set of regular expressions that identify a failed attempt in a log file.
sudo systemctl enable --now fail2ban
sudo systemctl status fail2ban
Verify version
fail2ban-client version
03 — Configuration Overview
fail2ban's configuration is split across several directories. Never edit the .conf files directly — they are overwritten on upgrades. Always use .local overrides.
Path
Purpose
/etc/fail2ban/fail2ban.conf
Global daemon settings (log level, socket, PID). Override via fail2ban.local.
/etc/fail2ban/jail.conf
Default jail definitions. Do not edit. Override via jail.local.
/etc/fail2ban/jail.local
Your active jail configuration. Create this file.
/etc/fail2ban/jail.d/
Drop-in jail snippets — one file per service is clean practice.
⚠ Always work in jail.local or jail.d/. Changes to jail.conf are silently lost on package upgrades.
Minimal jail.local skeleton
# /etc/fail2ban/jail.local
[DEFAULT]
# IPs / CIDRs that are NEVER banned — always whitelist your own admin IPs
ignoreip = 127.0.0.1/8 ::1 10.0.0.0/8 192.168.0.0/16
# How far back to look for failures (seconds)
findtime = 600
# Number of failures before a ban
maxretry = 5
# How long to ban (seconds). Use -1 for permanent.
bantime = 3600
# Action: ban + optional email. Use "%(action_mwl)s" to include log lines.
action = %(action_)s
Key DEFAULT parameters
Parameter
Default
Meaning
bantime
10m
Duration of a ban. Supports 1h, 1d, -1 (permanent).
findtime
10m
Time window in which maxretry failures trigger a ban.
maxretry
5
Number of failures within findtime before banning.
ignoreip
127.0.0.1/8
Whitelist — space-separated IPs/CIDRs/hostnames. Never banned.
bantime.increment
false
Exponentially increase ban duration for repeat offenders.
bantime.multiplier
1
Multiplier applied per successive ban (e.g. 2 doubles each time).
bantime.maxtime
—
Cap on incremental ban length (e.g. 1w).
backend
auto
Log-reading backend: systemd, pyinotify, polling.
usedns
warn
DNS lookup behaviour: yes / no / warn.
04 — SSH
SSH is the most common brute-force target on the internet. fail2ban's sshd jail watches /var/log/auth.log (Debian) or /var/log/secure (RHEL), or reads directly from the systemd journal.
/etc/fail2ban/jail.d/sshd.local
[sshd]
enabled = true
port = ssh # or the custom port: 2222
filter = sshd
backend = systemd # use "auto" if not running systemd
maxretry = 3
findtime = 300 # 5 minutes
bantime = 86400 # 24 hours — SSH deserves a long ban
logpath = /var/log/auth.log # Debian/Ubuntu
# logpath = /var/log/secure # RHEL/CentOS
✔ For systemd-based distributions (most modern servers), set backend = systemd and omit logpath — fail2ban reads the journal directly and catches failures before they even hit a log file.
05 — HTTP & HTTPS (Nginx / Apache)
Web servers are targeted by credential-stuffing, scanner bots, and path-traversal probes. fail2ban ships filters for both Nginx and Apache covering bad bots, authentication failures, and 4xx floods.
ℹ Verify your log paths. Ubuntu/Debian uses /var/log/apache2/; RHEL uses /var/log/httpd/. Nginx paths are typically under /var/log/nginx/ regardless of distro.
06 — FTP (vsftpd / ProFTPD / Pure-FTPd)
FTP is a favourite target for credential spray attacks. fail2ban includes dedicated filters for the three most common daemons.
⚠ FTP transmits credentials in plaintext. Consider migrating to SFTP (SSH subsystem) or FTPS (FTP over TLS) and blocking plain FTP entirely at the firewall.
07 — SMTP (Postfix / Exim)
Mail servers face two main threats: SASL authentication brute-force (credential attacks on port 587/465) and spam relay probes. fail2ban can block both.
Database ports exposed to the network (never a good idea, but it happens) need protection too. fail2ban can guard against repeated failed authentication attempts.
/etc/fail2ban/filter.d/postgresql.conf (if not present)
[Definition]
failregex = ^.*FATAL: password authentication failed for user ".*" .*$
^.*FATAL: no pg_hba.conf entry for host "".*$
ignoreregex =
⚠ Database ports should never be exposed to the public internet. Use SSH tunnelling, a VPN, or firewall rules to restrict access by IP before relying on fail2ban as a backstop.
10 — Writing a Custom Filter
When a service isn't covered by a shipped filter, write your own in five minutes. Filters live in /etc/fail2ban/filter.d/ and consist of a [Definition] section with one or more failregex patterns.
Anatomy of a filter file
# /etc/fail2ban/filter.d/myapp.conf
[INCLUDES]
before = common.conf # provides %(__prefix_line)s and other helpers
[Definition]
# <HOST> is the magic placeholder — fail2ban extracts the IP from here
failregex = ^%(__prefix_line)s.*Failed login from <HOST>.*$
^%(__prefix_line)s.*Invalid password for .* from <HOST>.*$
# Lines to silently ignore (internal health checks, localhost, etc.)
ignoreregex = ^.*127\.0\.0\.1.*$
Test a filter against a live log
# Dry-run: shows every line matched and the extracted IPs
fail2ban-regex /var/log/myapp/auth.log /etc/fail2ban/filter.d/myapp.conf
# Test against systemd journal
fail2ban-regex systemd-journal /etc/fail2ban/filter.d/sshd.conf
# Verbose output (shows unmatched lines too)
fail2ban-regex --print-all-nomatched /var/log/myapp/auth.log /etc/fail2ban/filter.d/myapp.conf
Standard syslog/journal prefix (date, host, service).
%(__hostname)s
The local hostname.
<ADDR>
Like <HOST> but also matches hostnames.
<CIDR>
CIDR notation match.
11 — Actions
An action defines what fail2ban does when it bans (and unbans) an IP. The default action inserts an iptables DROP rule. Several alternatives ship out of the box.
Action name
What it does
%(action_)s
Ban only — iptables DROP. No email. Recommended default.
%(action_mw)s
Ban + send email with whois report.
%(action_mwl)s
Ban + email with whois + matching log lines.
iptables-multiport
Ban across multiple ports in one rule.
iptables-allports
Ban the IP on all ports.
nftables-multiport
nftables equivalent of iptables-multiport.
cloudflare
Calls the Cloudflare API to block the IP at the edge.
sendmail-whois
Send email via sendmail with whois lookup.
Per-jail action override
[sshd]
enabled = true
filter = sshd
port = ssh
logpath = /var/log/auth.log
# Override the default action just for this jail
action = iptables-allports[name=sshd]
sendmail-whois[name=sshd, dest=admin@example.com, sender=fail2ban@example.com]
maxretry = 3
bantime = 86400
ℹ Ubuntu 22.04+ and Debian 11+ use nftables as the default backend. Set banaction = nftables-multiport in [DEFAULT] to avoid iptables legacy compatibility issues.
12 — Managing Bans with fail2ban-client
fail2ban-client is the CLI for interacting with the running daemon — checking status, manually banning/unbanning IPs, reloading config, and more.
Status & monitoring
# Overall daemon status
fail2ban-client status
# Status of a specific jail (shows banned IPs + counters)
fail2ban-client status sshd
# List all active jails
fail2ban-client status | grep "Jail list"
Manual ban / unban
# Manually ban an IP in a specific jail
fail2ban-client set sshd banip 203.0.113.42
# Unban an IP from a specific jail
fail2ban-client set sshd unbanip 203.0.113.42
# Unban an IP from ALL jails at once
fail2ban-client unban 203.0.113.42
# Permanently whitelist an IP at runtime (until restart)
fail2ban-client set sshd addignoreip 198.51.100.1
Config reload
# Reload all configuration without restarting the daemon
fail2ban-client reload
# Reload a single jail
fail2ban-client reload sshd
View currently banned IPs via iptables
# List all fail2ban chains
sudo iptables -L -n | grep -i fail2ban
# List banned IPs in the SSH chain
sudo iptables -L f2b-sshd -n --line-numbers
View fail2ban logs
# Follow live log output
sudo tail -f /var/log/fail2ban.log
# Filter for ban/unban events only
sudo grep "Ban\|Unban" /var/log/fail2ban.log
# systemd journal
sudo journalctl -u fail2ban -f
13 — Permanent Whitelist & Blacklist
The ignoreip directive whitelists addresses that fail2ban will never ban. For persistent manual bans, use a dedicated jail backed by a plain text file.
Whitelist — jail.local [DEFAULT]
[DEFAULT]
# Always whitelist your own IPs, monitoring systems, and CI runners
ignoreip = 127.0.0.1/8 ::1
10.0.0.0/8
192.168.0.0/16
203.0.113.10 # office static IP
198.51.100.0/24 # VPN subnet
# Permanently ban an IP via the blacklist jail
fail2ban-client set blacklist banip 203.0.113.99
ℹ For larger persistent blocklists consider ipset with the iptables-ipset-proto6 action — it handles tens of thousands of IPs far more efficiently than individual iptables rules.
14 — Email Notifications
fail2ban can send email alerts on ban events. You need a working local MTA (postfix, sendmail, or msmtp) or an SMTP relay configured.
jail.local — email action
[DEFAULT]
# Email settings
destemail = admin@example.com
sender = fail2ban@yourhostname.example.com
mta = sendmail # or "mail", "postfix"
# Action: ban + whois + log excerpt
action = %(action_mwl)s
⚠ Using %(action_mwl)s globally on busy servers generates enormous mail volume. Prefer banning silently (%(action_)s) by default and enable email only on high-value jails like sshd.
15 — Troubleshooting
Symptom
Cause & Fix
Jail not starting
Syntax error in .local file — run fail2ban-client -t to validate config before restarting.
IPs not getting banned
Check fail2ban-regex against your log file. Log path wrong, or log format changed. Verify backend setting.
Legitimate users banned
Add their IPs/CIDRs to ignoreip in [DEFAULT] and reload.
Bans not persisting after reboot
iptables rules are ephemeral. Use iptables-persistent or let fail2ban re-apply on startup (it does by default via /var/lib/fail2ban/fail2ban.sqlite3).
fail2ban not reading systemd journal
Set backend = systemd and ensure the python3-systemd package is installed.
nftables rules not applied
Set banaction = nftables-multiport in [DEFAULT] and ensure nftables package is installed.
High CPU / lag on large logs
Switch log backend from polling to pyinotify or systemd to avoid full-file re-scans.
Ban count always 0
The regex in the filter doesn't match your log format — use fail2ban-regex --print-all-matched to debug.
Ban applied but IP still connects
Another network path (CDN, proxy) is masking the real source IP. Check X-Forwarded-For log format settings.
Useful diagnostic commands
# Test configuration for syntax errors
fail2ban-client -t
# Test a filter regex against a log file
fail2ban-regex /var/log/auth.log /etc/fail2ban/filter.d/sshd.conf --print-all-matched
# Show current database of bans
sudo sqlite3 /var/lib/fail2ban/fail2ban.sqlite3 "SELECT * FROM bips;"
# Check which iptables chains fail2ban created
sudo iptables -L -n | grep f2b
16 — Quick Reference
Command
What it does
fail2ban-client status
List all active jails and their state
fail2ban-client status sshd
Show banned IPs and counters for the sshd jail
fail2ban-client set sshd banip <IP>
Manually ban an IP in the sshd jail
fail2ban-client set sshd unbanip <IP>
Unban an IP from the sshd jail
fail2ban-client unban <IP>
Unban an IP from all jails
fail2ban-client reload
Reload all config without restarting
fail2ban-client -t
Test config for syntax errors
fail2ban-regex <log> <filter>
Test a filter regex against a log file
systemctl restart fail2ban
Full restart (re-applies all bans from DB)
journalctl -u fail2ban -f
Follow live fail2ban log via systemd
✔ Start with sshd, verify it's working (fail2ban-client status sshd), then progressively enable jails for other services. Test each filter with fail2ban-regex before going live.