Fail2ban Tuning for SSH on a VPS: Jail Parameters, Metrics, and Lockout Safety

Most VPS operators install Fail2ban, enable the default SSH jail, and never look at it again. That default configuration bans an IP after five failed attempts for ten minutes — which is enough to stop a naive credential-stuffing bot but does nothing against a distributed attack that rotates source IPs every few tries. This article goes past installation and shows how to measure what Fail2ban is actually doing on your host, tune the jail parameters against real evidence, and avoid locking out legitimate users in the process. If you are still picking infrastructure for this kind of hardening, it helps to compare VPS providers that expose firewall and security-group controls.

What the Default SSH Jail Actually Does

Fail2ban tails log files through a filter, and when the filter regex matches enough failures inside a rolling window, the action bans the source IP for a defined duration. The three parameters that control this are the ones most people never touch:

  • maxretry — failures allowed before a ban
  • findtime — the rolling window in which those failures must occur
  • bantime — how long the ban lasts (-1 means permanent until manually removed)

The stock values are maxretry=5, findtime=10m, bantime=10m. The problem with a 10-minute ban is that a patient attacker simply waits it out. A slow, distributed attempt that stays under five failures per IP will never trip the jail at all.

Measuring Before You Tune

Never tune maxretry downward blindly — you will lock out your own team during a busy deploy. Start by measuring. fail2ban-client status sshd gives you the current banned count and the list of IPs:

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

For trend data, the log is more useful than the live status. Count bans over the last 24 hours:

sudo grep 'Ban ' /var/log/fail2ban.log | awk '{print $1}' | sort | uniq -c

If you see a steady stream of bans and a large banned-IP list, the jail is working and the threat is constant. If the list stays near zero while your auth log shows thousands of failed attempts, the attackers are staying under your threshold — raise the ban duration and lower the window, not the attempt count.

A Tuned SSH Jail Configuration

Put local overrides in /etc/fail2ban/jail.d/sshd.local so package upgrades do not clobber them. This configuration assumes key-based authentication is enforced separately and that you want aggressive bans without choking on false positives from a single flaky client:

[sshd]
enabled  = true
port     = ssh
filter   = sshd
backend  = systemd
maxretry = 4
findtime = 600
bantime  = 86400
ignoreip = 127.0.0.1/8 ::1

bantime = 86400 (24 hours) is the key change. Most distributed attacks reuse the same hosts across a day; a one-day ban materially raises the attacker’s cost. findtime = 600 tightens the rolling window so four failures inside ten minutes trip the jail. Always add your own management IPs to ignoreip — this is the single most common cause of self-lockout. If you manage servers with packet-level debugging tools, the same tcpdump and tshark workflow used for TLS troubleshooting applies to confirming which IPs are actually hitting port 22.

Recidive: Catching Repeat Offenders

A single jail only sees one service’s log. The recidive jail watches Fail2ban’s own log and bans IPs that trigger repeated bans across any service, for a much longer period:

[recidive]
enabled  = true
logpath  = /var/log/fail2ban.log
maxretry = 3
findtime = 86400
bantime  = 604800

An IP banned three times in one day is not a user with a fat-fingered password — it is a scan host. A one-week recidive ban removes it from the game entirely.

Validating the Configuration and the Filter

Before reloading, dry-run the parser against your real log to confirm the filter matches what your SSH daemon writes. This catches the classic case of a jail enabled but matching nothing because the log format changed:

sudo fail2ban-regex /var/log/auth.log /etc/fail2ban/filter.d/sshd.conf
sudo systemctl reload fail2ban
fail2ban-client status

The regex test prints a summary of matched versus missed lines. If the “Lines: matched” count is zero, your filter and your log backend are out of sync — an extremely common silent failure that makes administrators believe they are protected when they are not.

Safe Unbanning and Operational Habits

When you inevitably need to unban someone, do it by IP rather than by flushing the whole jail, so you keep the rest of the blocklist intact:

sudo fail2ban-client set sshd unbanip 203.0.113.7

Two habits prevent almost every Fail2ban incident: whitelist a jump host or management IP in ignoreip, and ensure you have a console (out-of-band) access path so a bad jail rule can never lock you out permanently. Once the jail is proven, the same principle of enforcing limits at the kernel level extends beyond bans — capping memory with cgroups v2 applies the identical “define a hard boundary and measure it” mindset to resource exhaustion.

The Takeaway

Fail2ban is not a set-and-forget tool. The default 10-minute ban is effectively decorative against modern distributed scanning. Measure your ban rate, tighten findtime, extend bantime to a full day, add a recidive jail for repeat offenders, and always verify the filter actually matches your log format. That combination turns a default installation into a genuine layer of defense — and the whitelist plus console-access habits ensure it never locks you out of your own server.

Leave a Reply