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.

SSH  ·  HTTP/HTTPS  ·  FTP  ·  SMTP  ·  IMAP/POP3  ·  MySQL  ·  Custom filters
01 — What is fail2ban?

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.

ComponentRole
FilterA set of regular expressions that identify a failed attempt in a log file.
JailCombines a filter + log path + thresholds (maxretry, findtime, bantime) + action.
ActionWhat happens on a ban: iptables/nftables rule, email alert, custom script, etc.
BackendHow fail2ban reads log files: pyinotify, systemd, polling.
02 — Installation
Debian / Ubuntu
sudo apt update && sudo apt install fail2ban
RHEL / Fedora / CentOS
# Fedora / RHEL 8+ / Rocky / AlmaLinux sudo dnf install epel-release && sudo dnf install fail2ban # CentOS 7 sudo yum install epel-release && sudo yum install fail2ban
Arch Linux
sudo pacman -S fail2ban
Enable and start
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.

PathPurpose
/etc/fail2ban/fail2ban.confGlobal daemon settings (log level, socket, PID). Override via fail2ban.local.
/etc/fail2ban/jail.confDefault jail definitions. Do not edit. Override via jail.local.
/etc/fail2ban/jail.localYour active jail configuration. Create this file.
/etc/fail2ban/jail.d/Drop-in jail snippets — one file per service is clean practice.
/etc/fail2ban/filter.d/Filter definitions (regex). Shipped filters live here; add custom ones too.
/etc/fail2ban/action.d/Action definitions (iptables, nftables, email, Cloudflare, etc.).
⚠  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
ParameterDefaultMeaning
bantime10mDuration of a ban. Supports 1h, 1d, -1 (permanent).
findtime10mTime window in which maxretry failures trigger a ban.
maxretry5Number of failures within findtime before banning.
ignoreip127.0.0.1/8Whitelist — space-separated IPs/CIDRs/hostnames. Never banned.
bantime.incrementfalseExponentially increase ban duration for repeat offenders.
bantime.multiplier1Multiplier applied per successive ban (e.g. 2 doubles each time).
bantime.maxtimeCap on incremental ban length (e.g. 1w).
backendautoLog-reading backend: systemd, pyinotify, polling.
usednswarnDNS 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
Aggressive recidivism policy (repeat offenders)
[sshd] enabled = true port = ssh filter = sshd backend = systemd maxretry = 3 findtime = 300 bantime = 3600 bantime.increment = true bantime.multiplier = 24 # 1h → 24h → 576h → ... bantime.maxtime = 2592000 # cap at 30 days
✔  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.

Nginx — /etc/fail2ban/jail.d/nginx.local
# Block IPs that trigger too many 4xx errors (scanners, bad bots) [nginx-http-auth] enabled = true port = http,https filter = nginx-http-auth logpath = /var/log/nginx/error.log maxretry = 5 bantime = 3600 # Block aggressive bot/scanner probes (404 flooding) [nginx-noscript] enabled = true port = http,https filter = nginx-noscript logpath = /var/log/nginx/access.log maxretry = 6 bantime = 86400 # Block >limit_req overflows (rate-limiting abuse) [nginx-limit-req] enabled = true port = http,https filter = nginx-limit-req logpath = /var/log/nginx/error.log maxretry = 10 findtime = 60 bantime = 7200 # Block access to hidden files (.git, .env, etc.) [nginx-badbots] enabled = true port = http,https filter = nginx-badbots logpath = /var/log/nginx/access.log maxretry = 2 bantime = 86400
Apache — /etc/fail2ban/jail.d/apache.local
# HTTP Basic Auth failures [apache-auth] enabled = true port = http,https filter = apache-auth logpath = /var/log/apache2/error.log maxretry = 5 bantime = 3600 # Scanner / bad bot probes [apache-badbots] enabled = true port = http,https filter = apache-badbots logpath = /var/log/apache2/access.log maxretry = 2 bantime = 172800 # 48 hours # Block exploit/shell injection attempts [apache-shellshock] enabled = true port = http,https filter = apache-shellshock logpath = /var/log/apache2/access.log maxretry = 1 # zero tolerance bantime = 604800 # 7 days # Overflows from mod_evasive or mod_security [apache-overflows] enabled = true port = http,https filter = apache-overflows logpath = /var/log/apache2/error.log maxretry = 2 bantime = 3600
ℹ  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.

/etc/fail2ban/jail.d/ftp.local
# vsftpd [vsftpd] enabled = true port = ftp,ftp-data,ftps,ftps-data filter = vsftpd logpath = /var/log/vsftpd.log maxretry = 5 bantime = 3600 # ProFTPD [proftpd] enabled = true port = ftp,ftp-data,ftps,ftps-data filter = proftpd logpath = /var/log/proftpd/proftpd.log maxretry = 5 bantime = 3600 # Pure-FTPd [pure-ftpd] enabled = true port = ftp,ftp-data,ftps,ftps-data filter = pure-ftpd logpath = /var/log/syslog # Debian # logpath = /var/log/messages # RHEL maxretry = 5 bantime = 3600
⚠  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.

/etc/fail2ban/jail.d/postfix.local
# Postfix SASL authentication failures [postfix-sasl] enabled = true port = smtp,465,587 filter = postfix-sasl logpath = /var/log/mail.log # Debian # logpath = /var/log/maillog # RHEL backend = auto maxretry = 5 bantime = 3600 # Postfix general (relay attempts, unknown users) [postfix] enabled = true port = smtp,465,587 filter = postfix logpath = /var/log/mail.log maxretry = 10 findtime = 600 bantime = 1800 # Dovecot IMAP/POP3 authentication (often co-located with Postfix) [dovecot] enabled = true port = pop3,pop3s,imap,imaps,submission,465,sieve filter = dovecot logpath = /var/log/mail.log maxretry = 5 bantime = 7200
/etc/fail2ban/jail.d/exim.local
# Exim authentication failures [exim] enabled = true port = smtp,465,587 filter = exim logpath = /var/log/exim4/mainlog # Debian maxretry = 10 bantime = 1800 [exim-spam] enabled = true port = smtp,465,587 filter = exim-spam logpath = /var/log/exim4/mainlog maxretry = 5 bantime = 86400
08 — IMAP & POP3 (Dovecot / Courier)

Webmail back-ends and thick clients authenticate via IMAP/POP3. These ports are constantly probed for stolen or leaked credentials.

/etc/fail2ban/jail.d/dovecot.local
[dovecot] enabled = true port = pop3,pop3s,imap,imaps,submission,465,sieve filter = dovecot backend = auto logpath = /var/log/mail.log maxretry = 5 findtime = 300 bantime = 7200 # Separate jail for Roundcube webmail login failures [roundcube-auth] enabled = true port = http,https filter = roundcube-auth logpath = /var/log/roundcube/errors.log maxretry = 5 bantime = 3600
/etc/fail2ban/jail.d/courier.local
[courier-smtp] enabled = true port = smtp,465,587 filter = couriersmtp logpath = /var/log/mail.log maxretry = 5 bantime = 3600 [courier-imap] enabled = true port = imap,imaps filter = courierimap logpath = /var/log/mail.log maxretry = 5 bantime = 3600 [courier-pop3] enabled = true port = pop3,pop3s filter = courierpop3 logpath = /var/log/mail.log maxretry = 5 bantime = 3600
09 — MySQL / MariaDB & PostgreSQL

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/jail.d/mysql.local
[mysqld-auth] enabled = true port = 3306 filter = mysqld-auth logpath = /var/log/mysql/error.log # Debian # logpath = /var/log/mysqld.log # RHEL maxretry = 5 bantime = 3600
/etc/fail2ban/filter.d/mysqld-auth.conf (if not present)
[Definition] failregex = ^%(__prefix_line)sAccess denied for user .*@''.*$ ignoreregex =
/etc/fail2ban/jail.d/postgresql.local
[postgresql] enabled = true port = 5432 filter = postgresql logpath = /var/log/postgresql/postgresql-*.log maxretry = 5 bantime = 3600
/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
Wire the filter into a jail
# /etc/fail2ban/jail.d/myapp.local [myapp] enabled = true port = 8080 filter = myapp # matches filter.d/myapp.conf logpath = /var/log/myapp/auth.log maxretry = 5 bantime = 3600
Useful regex shortcuts
TokenExpands to
<HOST>IPv4 and IPv6 address pattern — the ban target.
%(__prefix_line)sStandard syslog/journal prefix (date, host, service).
%(__hostname)sThe 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 nameWhat it does
%(action_)sBan only — iptables DROP. No email. Recommended default.
%(action_mw)sBan + send email with whois report.
%(action_mwl)sBan + email with whois + matching log lines.
iptables-multiportBan across multiple ports in one rule.
iptables-allportsBan the IP on all ports.
nftables-multiportnftables equivalent of iptables-multiport.
cloudflareCalls the Cloudflare API to block the IP at the edge.
sendmail-whoisSend 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
nftables action (modern distros)
# /etc/fail2ban/jail.local — switch globally to nftables [DEFAULT] banaction = nftables-multiport banaction_allports = nftables-allports
ℹ  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
Permanent blacklist jail
# /etc/fail2ban/jail.d/blacklist.local [blacklist] enabled = true filter = blacklist logpath = /dev/null # no log needed — bans come from banlist file banaction = iptables-allports bantime = -1 # permanent maxretry = 1
# /etc/fail2ban/filter.d/blacklist.conf [Definition] failregex = ignoreregex =
# 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
Per-jail email override (only alert on SSH)
[sshd] enabled = true port = ssh filter = sshd logpath = /var/log/auth.log maxretry = 3 bantime = 86400 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
SymptomCause & Fix
Jail not startingSyntax error in .local file — run fail2ban-client -t to validate config before restarting.
IPs not getting bannedCheck fail2ban-regex against your log file. Log path wrong, or log format changed. Verify backend setting.
Legitimate users bannedAdd their IPs/CIDRs to ignoreip in [DEFAULT] and reload.
Bans not persisting after rebootiptables 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 journalSet backend = systemd and ensure the python3-systemd package is installed.
nftables rules not appliedSet banaction = nftables-multiport in [DEFAULT] and ensure nftables package is installed.
High CPU / lag on large logsSwitch log backend from polling to pyinotify or systemd to avoid full-file re-scans.
Ban count always 0The regex in the filter doesn't match your log format — use fail2ban-regex --print-all-matched to debug.
Ban applied but IP still connectsAnother 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
CommandWhat it does
fail2ban-client statusList all active jails and their state
fail2ban-client status sshdShow 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 reloadReload all config without restarting
fail2ban-client -tTest config for syntax errors
fail2ban-regex <log> <filter>Test a filter regex against a log file
systemctl restart fail2banFull restart (re-applies all bans from DB)
journalctl -u fail2ban -fFollow 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.

fail2ban.org  ·  github.com/fail2ban/fail2ban v1.0+  ·  Python 3  ·  GPL-2.0