Get started
fail2ban best practices and recommended settings
A default fail2ban install bans for ten minutes and trusts whatever you put in /etc/fail2ban. This checklist turns it into a setup you can leave running — and is honest about what fail2ban can't do.
fail2ban best practices in short: use SSH keys first and treat fail2ban as an extra layer, whitelist your own IPs with ignoreip, use bantime = 1h, findtime = 10m, maxretry = 5 with bantime.increment = true, enable the recidive jail, read logs through the systemd backend, ban with nftables, test every filter with fail2ban-regex, keep /etc/fail2ban root-owned, and run a current release (1.1.1).
The checklist#
| # | Practice | Why |
|---|---|---|
| 1 | Key-based SSH, no password login | fail2ban limits guessing; keys make guessing pointless |
| 2 | Put your own IPs in ignoreip |
Avoid locking yourself out |
| 3 | Config only in .local files |
Upgrades don't overwrite your settings |
| 4 | bantime = 1h, findtime = 10m, maxretry = 5 |
Sensible baseline instead of 10-minute bans |
| 5 | bantime.increment = true |
Repeat offenders get longer bans automatically |
| 6 | Enable recidive |
Week-long, all-ports bans for persistent bots |
| 7 | backend = systemd where logs are in the journal |
Works on Debian 12+, Fedora, Arch; no missing-file errors |
| 8 | banaction = nftables (or your firewall's action) |
One set per jail, scales to many bans |
| 9 | Test filters and config before reloading | Silent "never bans" failures are common |
| 10 | Monitor bans and fail2ban's own log | Know it's working |
| 11 | Protect web apps and use the real client IP | SSH is rarely the only login |
| 12 | Keep fail2ban updated | Filter fixes and security fixes |
| 13 | Root-owned config, no sudo for fail2ban-client | Write access to actions = root access |
| 14 | Back up /etc/fail2ban |
Rebuild a server in minutes |
1. Fix authentication first#
fail2ban's own README is blunt about it: it reduces the rate of failed logins but can't eliminate the risk of weak authentication. Before you tune any jail:
- Use SSH keys and set
PasswordAuthentication noandPermitRootLogin prohibit-password(orno) insshd_config. - Use two-factor authentication on web panels that support it.
- Expose only what must be public; put admin interfaces behind a VPN.
With password login disabled, the sshd jail still earns its keep: it cuts log noise and stops bots from burning CPU on key exchanges. The SSH guide covers both sides.
2. Whitelist your own addresses with ignoreip#
[DEFAULT]
ignoreip = 127.0.0.1/8 ::1 192.168.1.0/24 203.0.113.10Add loopback, your LAN, your office or VPN egress IP, and monitoring hosts. ignoreself = true (the default) already covers the server's own addresses. Don't whitelist large public ranges "just in case" — every whitelisted address is one fail2ban will never stop. Since 1.1.1 you can keep the list in a file with ignoreip = file:/etc/fail2ban/ignoreip.list. Details: whitelist IPs with ignoreip.
3. Never edit jail.conf#
Put every change in /etc/fail2ban/jail.local or jail.d/*.local. fail2ban reads jail.conf → jail.d/*.conf → jail.local → jail.d/*.local, and later files win. Package upgrades replace .conf files but leave .local files alone. Copying the whole of jail.conf to jail.local is a common mistake: you freeze old defaults and miss upstream fixes. Only copy the lines you change — see the jail.local example.
4. Recommended bantime, findtime and maxretry#
The stock values (bantime = 10m, findtime = 10m, maxretry = 5) are deliberately cautious. A good baseline for internet-facing servers:
| Setting | Stock | Recommended | Notes |
|---|---|---|---|
bantime |
10m |
1h |
Starting value when increments are on |
findtime |
10m |
10m–1h |
Longer windows catch slow guessers |
maxretry |
5 |
5 (SSH with keys: 3) |
Lower values ban humans with typos |
bantime.increment |
off | true |
Doubles each repeat ban |
bantime.maxtime |
none | 1w–4w |
Caps the growth |
Avoid permanent bans (bantime = -1) as a default. Addresses get reassigned, firewall sets grow forever, and a single mistake locks out a legitimate user for good. Incremental bans with a cap get you most of the benefit. Use the bantime calculator to see what a policy does over time, and bantime, findtime and maxretry for the trade-offs.
5–6. Incremental bans and recidive#
[DEFAULT]
bantime = 1h
findtime = 10m
maxretry = 5
bantime.increment = true
bantime.maxtime = 2w
bantime.overalljails = true
[recidive]
enabled = truebantime.incrementuses the ban database to give the same IP 1h, 2h, 4h… Withbantime.overalljails = true, earlier bans in any jail count. More in incremental bans.recidivewatches fail2ban's own log and bans an IP banned 5 times in a day for a week, on all ports. See recidive.
Both depend on history, so keep it long enough. Raise the database purge age above your longest ban:
[DEFAULT]
dbpurgeage = 30dThe default 1d deletes ban records after a day. A week-long recidive ban would then not be restored after a restart.
7. Use the systemd backend where logs live in the journal#
On Debian 12+, Ubuntu 24.04+, Fedora, Arch, Raspberry Pi OS and Proxmox VE 8+, SSH failures exist only in the journal. Set backend = systemd for those jails (Debian 13 and Ubuntu do this for sshd already). Keep file-based jails such as nginx on backend = auto — a systemd backend ignores logpath. See backends and the systemd journal.
8. Ban with nftables (or your firewall's native action)#
Match the ban action to the firewall you actually run:
| Firewall | banaction |
|---|---|
| nftables (Debian, Ubuntu 24.04+, Arch) | nftables |
| firewalld (RHEL family, Fedora) | firewallcmd-rich-rules (default from fail2ban-firewalld) |
UFW, if you want bans in ufw status |
ufw |
| Legacy iptables | iptables-multiport / iptables-ipset |
The nftables action keeps each jail's bans in one set, so thousands of bans cost one rule, not thousands. Set banaction_allports to the matching all-ports variant for recidive. More in ban actions.
9. Test filters and config before you rely on them#
The most common fail2ban failure is silent: the jail starts, shows no errors, and never matches anything. Test before reloading:
sudo fail2ban-client -t # syntax and file check
sudo fail2ban-regex /var/log/nginx/error.log nginx-http-auth
sudo fail2ban-regex systemd-journal sshd # journal-based jails
sudo fail2ban-client reloadfail2ban-client -t prints OK: configuration test is successful. fail2ban-regex reports how many lines matched — zero matches on a log that clearly has failures means the filter or date format is wrong. Use the regex tester to develop custom filters, and keep regexes anchored and specific: overly broad patterns are slow and can be abused to ban the wrong IP. Guide: fail2ban-regex.
10. Monitor fail2ban#
sudo fail2ban-client status --all
sudo fail2ban-client banned
sudo grep -E 'NOTICE|WARNING|ERROR' /var/log/fail2ban.log | tail -50Watch for:
- A jail whose "Total failed" never grows — it isn't reading anything.
ERRORlines from actions — bans are logged but not applied.- Your own IPs in the ban list.
For dashboards and alerts, export metrics to Prometheus (monitoring guide), or analyze an existing log with the log analyzer. Email on every ban (action_mwl) gets noisy on busy servers; reserve it for low-traffic jails.
11. Protect web apps — with the real client IP#
Bots try SSH, but they also try /wp-login.php, /.env, basic-auth prompts and app logins. Enable jails for what you expose: nginx-http-auth, nginx-botsearch, nginx-limit-req, apache-auth, plus app filters such as WordPress, Nextcloud or Vaultwarden (see guides).
Behind a reverse proxy, CDN or tunnel, make sure the application logs the client's address, not the proxy's. Otherwise fail2ban bans your own proxy or Cloudflare — here is the fix. For sites behind Cloudflare, also ban at the edge with the Cloudflare guide.
12. Keep fail2ban updated#
Filters change as software changes its log formats, and fail2ban has had security fixes in filters. Release 1.1.1, for example:
- matches the
sshd-sessionprocess name that OpenSSH 9.8+ uses; - fixes catastrophic regex backtracking (ReDoS) in the dovecot and domino-smtp filters (GHSA-33wh-ccjc-p397).
Install your distribution's updates, check fail2ban-client --version, and read versions and changelog and security advisories when upgrading. If your distribution ships an old release and you need a fix, override just the affected filter in filter.d/<name>.local instead of replacing the whole package.
13. Lock down permissions: action.d is root code#
fail2ban-server runs as root and executes the commands in action.d (and the ignorecommand) as root. Anyone who can write to /etc/fail2ban — or who can talk to fail2ban's socket — can run commands as root. This is a well-known privilege-escalation path when a non-root user or group has write access to action.d.
# everything should be owned by root and not group/world-writable
sudo find /etc/fail2ban \( ! -user root -o -perm /022 \) -lsNo output is what you want. Also:
- Don't give users
sudo fail2ban-client. The client can redefine an action'sactionbancommand at runtime, so it's equivalent to full root. - Keep the socket at its default (
/var/run/fail2ban/fail2ban.sock, mode 0600, root). - Review third-party filters and actions before dropping them into
/etc/fail2ban, just like any script you run as root.
14. Back up and version your configuration#
Everything that makes your setup yours is in /etc/fail2ban/*.local, jail.d/, and custom files in filter.d/ and action.d/:
sudo tar czf fail2ban-config-$(date +%F).tar.gz /etc/fail2banBetter: keep /etc/fail2ban (or just your .local files) in a Git repository or your configuration management tool, so every change is reviewed and reversible. The ban database (/var/lib/fail2ban/fail2ban.sqlite3) doesn't need backing up — it rebuilds itself.
Don't rely on fail2ban alone#
fail2ban is reactive: it only acts after an application has logged a failure. It doesn't help against:
- a single successful login with a stolen password or key;
- exploits that don't produce a failed-login log line;
- distributed attacks where thousands of IPs each stay below
maxretry; - volumetric DDoS — the traffic still reaches your network.
Pair it with a default-deny firewall, key-based authentication, prompt OS updates and, for web apps, a WAF or rate limiting. If you need shared block lists across many servers, compare fail2ban alternatives such as CrowdSec.
FAQ#
What are the recommended fail2ban settings?#
A good baseline is bantime = 1h, findtime = 10m, maxretry = 5, bantime.increment = true with bantime.maxtime = 1w or more, your own IPs in ignoreip, the recidive jail enabled, backend = systemd for journal-based services and an nftables or firewalld ban action. Put it all in jail.local.
Should I set fail2ban bans to permanent?#
Usually not. Permanent bans (bantime = -1) grow your firewall sets forever and can lock out reassigned or shared IPs for good. Incremental bans (bantime.increment = true with a bantime.maxtime of a week or more) plus the recidive jail block persistent attackers almost as well, and still let mistakes expire.
Is fail2ban a security risk?#
It runs as root and executes shell commands from action.d, so writable config files or sudo access to fail2ban-client amount to root access. Keep /etc/fail2ban root-owned and not group- or world-writable, don't delegate fail2ban-client via sudo, and keep fail2ban updated for filter security fixes.
Spotted something wrong or out of date? Tell us — every page is checked against the current fail2ban release.