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.

Updated 8 min readChecked against fail2ban 1.1.1

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 no and PermitRootLogin prohibit-password (or no) in sshd_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#

/etc/fail2ban/jail.local
[DEFAULT]
ignoreip = 127.0.0.1/8 ::1 192.168.1.0/24 203.0.113.10

Add 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.

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#

/etc/fail2ban/jail.local
[DEFAULT]
bantime  = 1h
findtime = 10m
maxretry = 5
bantime.increment = true
bantime.maxtime = 2w
bantime.overalljails = true

[recidive]
enabled = true
  • bantime.increment uses the ban database to give the same IP 1h, 2h, 4h… With bantime.overalljails = true, earlier bans in any jail count. More in incremental bans.
  • recidive watches 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:

/etc/fail2ban/fail2ban.local
[DEFAULT]
dbpurgeage = 30d

The 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:

bash
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 reload

fail2ban-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#

bash
sudo fail2ban-client status --all
sudo fail2ban-client banned
sudo grep -E 'NOTICE|WARNING|ERROR' /var/log/fail2ban.log | tail -50

Watch for:

  • A jail whose "Total failed" never grows — it isn't reading anything.
  • ERROR lines 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-session process 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.

bash
# everything should be owned by root and not group/world-writable
sudo find /etc/fail2ban \( ! -user root -o -perm /022 \) -ls

No output is what you want. Also:

  • Don't give users sudo fail2ban-client. The client can redefine an action's actionban command 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/:

bash
sudo tar czf fail2ban-config-$(date +%F).tar.gz /etc/fail2ban

Better: 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#

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.