Fail2ban Jail Tuning on a VPS: findtime, maxretry and Testing Without Locking Yourself Out

The default fail2ban jail bans an IP after five failed SSH attempts inside ten minutes, for ten minutes. On an internet-facing VPS that is close to useless: botnets rotate source addresses faster than a ten-minute ban expires, and a legitimate user mistyping a key twice can get caught. Effective protection comes from tuning three numbers — maxretry, findtime and bantime — plus enabling the recidive jail and testing the whole thing deliberately instead of discovering the config when you are locked out.

Fail2ban is a wrapper around your host firewall, so it needs a provider that grants full root and lets you own the nftables/iptables ruleset. If you are picking a host, compare VPS plans here and confirm root access and a dedicated IPv4 before assuming fail2ban will behave the same way everywhere. The same comparison shows which plans give you the connection-tracking headroom that thousands of banned IPs will consume.

Install and Understand the Three Knobs

apt update && apt install -y fail2ban
systemctl enable --now fail2ban
fail2ban-client status sshd

The behaviour of every jail is governed by:

  • maxretry — failures permitted within the window before a ban.
  • findtime — the sliding window, in seconds, over which failures are counted.
  • bantime — how long the ban lasts; increase it for repeat offenders.

Tune the SSH Jail

Never edit files in /etc/fail2ban/jail.conf — they are overwritten on upgrade. Create /etc/fail2ban/jail.local:

[DEFAULT]
ignoreip = 127.0.0.1/8 ::1
backend = systemd
bantime.increment = true
bantime.factor = 2
bantime.maxtime = 48h
findtime = 10m
maxretry = 4

[sshd]
enabled = true
port = ssh
maxretry = 3
findtime = 60m
bantime = 1h

Three settings carry the weight. backend = systemd makes fail2ban read the journal rather than a log file, which is correct for modern Debian and Ubuntu. bantime.increment = true with a factor of 2 doubles each ban for repeat offenders up to 48 hours — this is what actually neutralises a persistent scanner. And a wide findtime of one hour catches slow brute forces that pace attempts to slip under a short window.

Override Instead of Editing

Remember that jail.local overrides ship defaults per jail. To confirm what a jail is actually using, ask fail2ban, not the file:

fail2ban-client get sshd maxretry
fail2ban-client get sshd findtime
fail2ban-client get sshd bantime
fail2ban-client status sshd

After any change, run fail2ban-client reload and re-read the live values so you are never reasoning about a value that was not applied.

Add a Web Jail

If Nginx runs on the box, a jail on repeated 404s from a single IP blocks directory scanners:

[nginx-botsearch]
enabled = true
port = http,https
filter = nginx-botsearch
logpath = /var/log/nginx/access.log
maxretry = 2
bantime = 24h

Test the filter against your own log before enabling it, because a mis-shaped regex can ban normal visitors: fail2ban-regex /var/log/nginx/access.log /etc/fail2ban/filter.d/nginx-botsearch.conf. The summary line tells you which lines the jail would have matched.

Enable the Recidive Jail

Recidive watches fail2ban’s own ban actions and bans IPs that trip any jail repeatedly:

[recidive]
enabled = true
logpath = /var/log/fail2ban.log
banaction = %(banaction_allports)s
bantime = 1w
findtime = 1d
maxretry = 5

This escalates an address that keeps returning from a one-hour SSH ban to a week-long all-ports ban — the single highest-value jail to enable on a public VPS, because it turns many rotating one-hour bans into one durable block.

Test Without Locking Yourself Out

Before trusting the config, verify the ban path from a second machine (or a second IP), never your only session:

# from test host: trigger failures
for i in $(seq 1 5); do ssh -o BatchMode=yes wrong@your-vps; done
# on the VPS: confirm the ban landed
fail2ban-client status sshd | grep Banned
# unban immediately
fail2ban-client set sshd unbanip <test-ip>

Always keep an ignoreip entry for your management address or jump host. Combined with bantime.increment and the recidive jail, tuned jails turn background SSH noise from a daily log flood into a handful of long-lived bans you can audit with fail2ban-client status.

Watch the Ban Database Over Time

Because bantime.increment stores per-IP history in /var/lib/fail2ban/fail2ban.sqlite3, that file grows with every distinct offender. On a small VPS under heavy scanning it can reach tens of megabytes, and a corrupt database stops bans silently. Check both size and integrity periodically:

du -h /var/lib/fail2ban/fail2ban.sqlite3
sqlite3 /var/lib/fail2ban/fail2ban.sqlite3 "PRAGMA quick_check;"
fail2ban-client banned | wc -l

A quick_check that returns anything other than ok means the ban history is unreliable — stop fail2ban, back the file up, and let it rebuild rather than discovering at 2 a.m. that an offending IP was never blocked.

Fail2ban Is Not a Substitute for Keys

Bans reduce log noise and stop opportunistic scanners; they do not stop a targeted attacker with a valid key. Keep PasswordAuthentication no, disable root login, and treat fail2ban as the cheap outer layer it is. With the jails above, an aggressive SSH rate, and keys only, the background hum of the internet becomes a number you check once a week instead of an incident.

Leave a Reply