Fail2ban watches log files for repeated authentication failures and installs temporary firewall bans against the offending addresses. It is the long-standing baseline for stopping SSH password brute force on a single Linux host.
Our verdict on Fail2ban#
Fail2ban is still correct for one job: stopping password brute force against SSH on a single host, with no network dependency and nothing phoning home. The defaults are conservative enough to be worth knowing before you rely on them. bantime is 10 minutes, findtime is 10 minutes and maxretry is 5, so five failures inside ten minutes earns a ten minute ban, which a distributed scanner will barely notice. Set bantime = 1w and maxretry = 3 or do not bother installing it. The comparison with CrowdSec is not about detection quality, it is about who maintains the patterns: Fail2ban's live in your jail.local and break silently when an upstream log format changes. Release cadence is the other honest fact. 1.1.0 shipped in April 2024, 1.1.1 not until August 2026. It is not abandoned so much as finished, and for SSH on one box that is genuinely fine.
Who Fail2ban is for#
Choose it if
- A single VPS with SSH exposed and little else, where you want one distribution package and no daemon talking to the internet.
- Air-gapped or compliance-constrained hosts where sending observed addresses to a third party is not permitted.
- Anyone with working
jail.localfiles and no concrete reason to migrate; the migration cost is real and the benefit is incremental. - Banning on something unusual: any log line you can write a regex against becomes a jail, including application logs nobody else has rules for.
Look elsewhere if
- Your services run in containers and log to stdout. There is no file to tail without extra plumbing, and iptables rules land in the wrong chain anyway.
- You want curated detection rules maintained by someone else. That is CrowdSec's actual pitch and it is a fair one.
- You are behind a reverse proxy or CDN and have not fixed real-IP logging. You will ban the proxy and take yourself offline.
What running it actually looks like#
A host package rather than a container: apt install fail2ban, then never edit jail.conf. Copy the settings you want into /etc/fail2ban/jail.local, because package upgrades overwrite jail.conf without asking. Everything ships with enabled = false; on Debian and Ubuntu only the sshd jail is turned on for you. Set backend to systemd if your distribution logs authentication to the journal rather than /var/log/auth.log, which is the single most common reason a fresh install detects nothing at all. banaction defaults to iptables-multiport and needs changing to the nftables equivalent on nftables-only systems. Put your own addresses in ignoreip before enabling anything. Test with fail2ban-regex /var/log/auth.log /etc/fail2ban/filter.d/sshd.conf rather than by failing logins yourself, then confirm with fail2ban-client status sshd.
Resource profile#
- Memory
- 40 MB idle, around 80 MB in ordinary use. Estimated from ordinary single-host installs, not a documented minimum. A Python daemon with one thread per jail; memory grows with the number of enabled jails and the size of the failregex set, not with traffic.
- CPU and acceleration
- Regex matching against every new log line. A handful of jails on a quiet host is free; twenty jails against a busy web access log is measurable in `top`, and this is where CrowdSec's compiled parsers win.
- Storage growth
- One SQLite file, purged at `dbpurgeage = 1d` by default, so it never grows. Its real storage cost is effectively zero.
- Operational load
- 2 of 5, Light. Install, write `jail.local`, and it usually stays working for years with no attention. It is not a 1 because the patterns are yours to maintain: when an upstream log format changes, a jail silently stops matching and nothing anywhere reports it.
Figures describe a small single-household install and are the working assumptions behind our stack planner. Your numbers will differ with library size, user count and hardware. See methodology.
Data and backup surface#
Back up /etc/fail2ban/ and nothing else. jail.local, jail.d/, and any custom filter.d/*.conf and action.d/*.conf are the entire state that matters, and they are small text files that belong in whatever configuration repository you already keep. /var/lib/fail2ban/fail2ban.sqlite3 holds current bans and is purged at one day by default (dbpurgeage = 1d), so it is not worth backing up: bans expire, and after a restart the daemon re-reads recent log history and re-establishes what it needs. Restoring is copying the configuration back and running systemctl restart fail2ban, then checking fail2ban-client status shows the jails you expect.
Derived backup shape
SQLite backup. sqlite3 <db> ".backup out.db" while running, or stop the container and copy. A live cp of a WAL-mode SQLite file can restore corrupt.
Traps and surprises#
Editing jail.conf instead of jail.local
jail.conf is shipped by the package and replaced on upgrade, so every customization you make there disappears at some unpredictable future apt upgrade, usually months later and silently. The file even says so at the top. Put overrides in /etc/fail2ban/jail.local or drop-ins under /etc/fail2ban/jail.d/, which are never touched by the package, and keep jail.conf as read-only reference for the option names.
The systemd journal backend
backend = auto guesses, and on distributions that no longer write /var/log/auth.log the guess can leave a jail watching a file that never gets a line. The jail shows as enabled, the status shows zero failures, and you conclude nobody is attacking you. Set backend = systemd explicitly on journal-only systems and confirm with fail2ban-client status sshd that the failure counter actually moves after a deliberate bad login.
Docker published ports bypass the INPUT chain
Docker inserts its own iptables rules, and traffic to a published container port is handled in the DOCKER chain before it ever reaches INPUT, where iptables-multiport puts its bans. So Fail2ban happily bans an address that keeps reaching your containerized service. The fix is an action targeting the DOCKER-USER chain, which Docker leaves for exactly this purpose, or running enforcement in front of Docker entirely.
The default ban is ten minutes
Out of the box, bantime = 10m, findtime = 10m and maxretry = 5. Against a botnet cycling thousands of source addresses, a ten minute ban after five attempts is close to no protection while feeling like protection. Raise bantime to days or a week, drop maxretry to 3, and consider the recidive jail, which watches Fail2ban's own log and applies long bans to repeat offenders.
Behind a proxy you ban the proxy
If nginx, Traefik or Cloudflare sits in front and your application logs the proxy's address, the jail matches on that address and Fail2ban blocks all traffic from your own front end. This takes the whole site down and looks like a proxy fault. Configure real-IP forwarding in the web server so log lines carry the client address, and put the proxy's own addresses in ignoreip so a misconfiguration cannot lock everyone out.
Specifications#
- Category
- Security and update hygiene
- Licence
- GPL-2.0-or-later (Copyleft)
- Written in
- Python
- First release
- 2004
- Maturity
- Mature
- Datastore
- SQLite at `/var/lib/fail2ban/fail2ban.sqlite3`
- Default ports
- none
- Ships as
- deb/rpm package, docker, source install
- arm64 builds
- Yes
- Identity
- Not applicable. No accounts, no web interface. `fail2ban-client` talks to the daemon over a Unix socket at `/var/run/fail2ban/fail2ban.sock`.
- Replaces
- Cloudflare rate limiting, AWS WAF managed rules, DenyHosts
- Project site
- www.fail2ban.org
- Source
- github.com/fail2ban/fail2ban
- Documentation
- github.com/fail2ban/fail2ban/wiki
Alternatives to Fail2ban#
Everything else in security and update hygiene, closest in operational weight first.
| Tool | Ops load | Idle RAM | Licence |
|---|---|---|---|
| CrowdSec | 2, Light | 120 MB | MIT |
| Watchtower | 2, Light | 15 MB | Apache-2.0 |
| Diun | 1, Set and forget | 25 MB | MIT |
| Wazuh | 5, Project-grade | 6144 MB | GPL-2.0 |
Sources#
Facts on this page were checked against the project's own material. Where a number is our own estimate rather than a published figure, the text says so.
Published . Last reviewed . Found something out of date? Tell us and we will fix it and log the change.