DevTools Hub

Search tools

Search for a developer tool

fail2ban Explained

Part of the Linux Security Toolkit

Any server with SSH exposed to the internet gets probed constantly — automated scanners trying common usernames and password lists against port 22, essentially all day, every day, regardless of how obscure the server is. fail2ban is the tool that watches for exactly that pattern and responds automatically: after a configured number of failed attempts within a time window, it firewalls off the source address for a set period, turning a slow, persistent guessing attempt into one that gets cut off quickly. This post covers how its configuration actually works and the settings most likely to be wrong by accident.

The three numbers that define a jail

fail2ban organizes its configuration into jails — one per service it watches, each with its own settings. Three values define how a jail actually behaves:

[sshd]
enabled = true
maxretry = 5
findtime = 10m
bantime = 1h

maxretry is how many failures are allowed before a ban triggers. findtime is the window those failures have to happen within — five failures spread across a week wouldn't trigger a ban with a 10-minute findtime, but five within ten minutes would. bantime is how long the resulting ban actually lasts. Modern fail2ban accepts suffixed durations (10m, 1h, 1d) as well as plain seconds, and a bantime of -1 specifically means permanent, until someone manually unbans the address.

Getting the numbers wrong in either direction

A bantime that's too short barely slows anything down — a persistent automated scanner can simply wait out a five-minute ban and resume exactly where it left off, at which point the jail is providing very little real protection despite technically functioning. Anything under roughly five minutes is worth reconsidering unless there's a specific reason for it.

A maxretry that's too low creates a different problem: a legitimate user who mistypes their password a couple of times in a row looks identical to an automated guessing attempt from fail2ban's perspective, and gets banned the same way. Setting maxretry to 1 or 2 optimizes for banning attackers slightly faster at the real cost of locking out your own team more often than intended — a tradeoff worth making deliberately, not by accident.

ignoreip: the exemption list that can quietly disable everything

ignoreip lists addresses or ranges that are never banned, no matter how many failures come from them — the mechanism for exempting a trusted office network or a known administrative IP from ever being locked out by its own team's typos. The failure mode worth watching for is a range broad enough to defeat the jail's whole purpose:

# Reasonable — exempts localhost and one trusted range
ignoreip = 127.0.0.1/8 10.0.0.0/24

# Defeats the jail entirely — exempts every possible address
ignoreip = 0.0.0.0/0

0.0.0.0/0 (or ::/0 for IPv6) matches every address that exists, silently turning an otherwise correctly configured jail into a no-op. This isn't a hypothetical — it's an easy typo to make when meaning to exempt one specific broad-looking range and instead writing the one that means "everything."

Where DEFAULT settings and jail-specific settings interact

Values set in the [DEFAULT] section apply to every jail that doesn't override them individually — a global bantime and findtime in [DEFAULT], with only [sshd] getting its own tighter maxretry, is a common and reasonable pattern. The trap is assuming a jail inherits sensible values just because it's enabled — a jail can be turned on with enabled = true and nothing else specified, silently relying on whatever the [DEFAULT] section (or fail2ban's own built-in defaults, if [DEFAULT] doesn't set them either) happens to provide.

jail.conf vs. jail.local

fail2ban ships its own defaults in jail.conf, but that file is expected to be overwritten on every package update — customizations belong in jail.local (or individual files under jail.d/) specifically so an upgrade doesn't silently wipe out a carefully tuned configuration. Editing jail.conf directly works until the next update replaces it.

A jail that parses fine but does nothing

Because the configuration format is forgiving INI syntax, a section can be completely well-formed and still not actually be active — missing enabled = true entirely, or having it set to anything other than true, produces a file that loads without error but protects nothing. This is easy to miss specifically because nothing about the syntax looks wrong; the jail is just quietly not running.

How a ban actually gets enforced

fail2ban doesn't implement its own network filtering — it watches log files for matching failure patterns, and when a ban triggers, it hands the actual blocking off to whatever firewall backend the jail is configured to use. On most modern distros that's the same underlying mechanism covered in Linux Firewall Guide — an action setting in the jail configuration determines whether it drives UFW, firewalld, or a raw iptables/nftables rule directly:

[DEFAULT]
banaction = ufw

This is why a fail2ban ban and a firewall rule can look identical from the outside — a banned address genuinely does get an explicit deny rule inserted for it, added and later removed automatically as bans start and expire. A mismatch here (fail2ban configured to drive a backend that isn't actually the firewall running on the box) is a real, if uncommon, way for bans to appear to succeed in fail2ban's own logs while doing nothing to actual traffic — worth confirming the configured banaction matches whichever firewall tool the server is genuinely using.

Checking what's actually happening, live

Reading a config file only shows intent — fail2ban-client shows the real, current state of a running instance:

sudo fail2ban-client status sshd       # currently banned IPs, total failed/banned counts
sudo fail2ban-client unban 203.0.113.5 # manually lift one ban immediately

This is the fastest way to confirm a jail is genuinely active and doing something — rather than inferring it from the config alone — and the direct fix for the common "a legitimate teammate got banned by mistake" situation, without needing to restart the whole fail2ban service just to clear one address.

Common mistakes

  • A bantime short enough to be waited out. Under about five minutes rarely accomplishes much against a persistent automated attempt.
  • A maxretry low enough to catch legitimate typos. One or two attempts is often too strict for real-world human error.
  • An ignoreip range broader than intended. Especially 0.0.0.0/0, which exempts literally everyone.
  • A jail that's enabled = true but relies on defaults nobody actually set. Worth confirming what maxretry and bantime a jail is actually using, not just that it's turned on.

FAQ

Does fail2ban replace the need for key-based SSH authentication?

No — it's a complementary layer, not a substitute. Disabling SSH password authentication entirely removes brute-force guessing as an attack path outright; fail2ban is what still helps when password authentication has to stay enabled for some reason, or against other services that don't have an equivalent all-or-nothing fix.

Can fail2ban accidentally ban a legitimate user?

Yes — a user who mistypes their password several times in a row looks identical to an automated guessing attempt from fail2ban's perspective, and gets banned the same way. This is exactly why maxretry shouldn't be set too low, and why ignoreip exists for trusted addresses (like an office's static IP) that should never be banned regardless of failed attempts.

What's the difference between findtime and bantime?

findtime is the window failures are counted within — maxretry failures have to happen inside this window to trigger a ban. bantime is how long the ban itself lasts once triggered. A short findtime with a low maxretry bans quickly; a long bantime keeps that ban in effect for longer once it happens.

Does fail2ban work for services other than SSH?

Yes — it ships with filters for many common services (web server authentication, mail servers, and others) and can be configured with a custom filter for practically any service that writes failed-attempt information to a log file. SSH is simply the most commonly configured jail since it's the most universally exposed service.

Is a permanent ban (bantime = -1) ever the right choice?

For a jail where legitimate users could plausibly trigger it, permanent bans risk locking someone out indefinitely over a mistake with no automatic recovery. For a jail specifically watching for clearly malicious, unambiguous activity, a permanent ban until manually reviewed and removed is a defensible, deliberate choice.

Try it yourself

fail2ban Jail Config Validator checks a jail.local file for every issue covered here — short ban times, risky ignoreip ranges, and jails that parse but were never enabled — entirely in your browser. For how fail2ban fits into a broader logging strategy, see Linux Security.

Related tools