Infrastructure engineering for East Africa's operators, platforms and regulators See the work →
fail2ban vs PerSourcePenalties: Do You Still Need fail2ban?
Linux 8 min read

fail2ban vs PerSourcePenalties: Do You Still Need fail2ban?

PerSourcePenalties is on by default in Ubuntu 26.04. We timed it against fail2ban: the guesser it never refused, and the colleague it locked out.

CE
CloudSpinx Engineering
Infrastructure Engineering, Virtualization, Networking, Automation
8 November 2026
fail2banopensshpersourcepenaltiesssh

Every time a connection tries a password or a key and fails, sshd on Ubuntu 26.04 adds five seconds to a clock it keeps for that source address. Once the clock runs more than 15 seconds ahead of now, it turns the address away until the clock runs out. That is PerSourcePenalties, on by default since OpenSSH 9.8, and its arithmetic settles most of the fail2ban vs PerSourcePenalties question before you install anything.

We ran a password guesser and a few innocent clients against an Ubuntu 26.04 server in our Proxmox lab, stock apart from password logins switched on, with the clients in network namespaces so we chose who shared an address. Then we ran the guesser again with fail2ban 1.1.0 from the Ubuntu archive. sshd's penalty stopped a guesser making one guess per connection after six or seven misses, for 16 to 18 seconds at a time. In 135 seconds of trying, it never stopped one making six guesses per connection. And every time it refused the guesser, it refused a colleague on the guesser's address too.

What PerSourcePenalties counts

The penalty is charged when a connection ends, not per password. sshd's listener reads how each pre-authentication session process exited: a client that tried to authenticate and failed costs 5 seconds (authfail), one that hung up without trying costs 1 (noauth), a crash costs 90. Penalties for one address add up on one clock. Once the clock is more than 15 seconds ahead (min), new connections from that address are dropped before the SSH banner until it runs out, and it never runs more than 10 minutes ahead (max). These are the defaults sshd -T printed on the lab server, OpenSSH 10.2p1, and the sshd_config manual explains each keyword:

persourcepenalties crash:90 authfail:5 noauth:1 grace-exceeded:10 refuseconnection:10 max:600 min:15 max-sources4:65536 max-sources6:65536 overflow:permissive overflow6:permissive
persourcenetblocksize 32:128
persourcepenaltyexemptlist none

Two details in OpenSSH's srclimit.c decide the rest. A dropped connection adds nothing to the clock, and a record whose time has run out is deleted. So a guesser working one connection at a time never builds toward the 10-minute cap: each time it is let back in, it starts from zero. In one run our guesser retried 2,661 times during a single refusal and was still let back in about 18 seconds after it began, the same as in the run where it retried once a second.

Only failed logins that land together reach past that. Eight one-guess connections ending within 0.07 seconds of each other kept the address out for about 41 seconds, although the journal said activating ipv4 penalty of 20 seconds. That line is written at the fourth failure, when the clock first passes 15, and the other four add their 5 seconds without another line.

What OpenSSH PerSourcePenalties did to two password guessers on Ubuntu 26.04
What OpenSSH PerSourcePenalties did to two password guessers on Ubuntu 26.04Two timelines in seconds from the first guess, both against an Ubuntu 26.04 server, stock apart from password logins switched on, with OpenSSH 10.2p1 and default PerSourcePenalties. A guesser making one wrong password per connection was refused from 20.4 to about 38.5 seconds and again from 56.0 to about 72.2, and a colleague doing key logins from the same address was refused in both windows. A guesser making six wrong passwords per connection finished a connection every 15.7 to 17.1 seconds and was never refused.ONE WRONG PASSWORD PER CONNECTION · 2.9 s EACH7 wrongrefused, colleague too6 wrongrefused20.4sclock passes 15 s38.5slet in, clock at 056spasses 15 s againSIX WRONG PASSWORDS PER CONNECTION · SAME SERVER6 wrong6 wrong6 wrong6 wrong15.7s+5 s, run outby 20.7 s49.8snever refused,colleague never either
Each failed connection adds 5 s to the address's clock and refusal starts only once it is more than 15 s ahead. One guess per connection got there at the sixth or seventh miss. Six per connection took about 17 s each, so each 5 s ran out about 12 s before the next arrived.

The guesser PerSourcePenalties never refused

Each wrong password took about 2.9 seconds on the lab server. Making one guess per connection banks 5 seconds every 2.9, so the clock gains about 2.1 seconds a miss and crosses 15 at the sixth or seventh. Making all six guesses that MaxAuthTries allows banks the same 5 seconds once per connection, and those connections took 15.7 to 17.1 seconds each. Each penalty had run out long before the next one arrived.

Over 135 seconds that guesser put 48 passwords to the server and was never refused. The one-guess guesser managed 14 in 76 seconds. Spending the maximum per connection got through faster than being throttled: about 21 guesses a minute against 11.

We have not measured what real attack traffic sends per connection, so read that as the arithmetic of the default rather than a census of botnets. The arithmetic is the point. At default settings PerSourcePenalties slows a fast, careless guesser and leaves alone one that works one connection at a time and spends longer on each than the 5 seconds it is charged for it.

kex_exchange_identification: read: Connection reset by peer

That is what a refused user saw at the default log level when sshd's close arrived as a reset, as 106 of 182 refused logins did across four of our runs:

$ ssh -i legit_key [email protected] true
kex_exchange_identification: read: Connection reset by peer
Connection reset by 192.0.2.1 port 22

The other 76 closed cleanly, and for those OpenSSH's client source prints Connection closed by 192.0.2.1 port 22 instead.

Only ssh -v shows the reason sshd sent before hanging up, banner line 0: Not allowed at this time. The server logs one drop connection #0 from [192.0.2.2]:37248 on [192.0.2.1]:22 penalty: failed authentication line per refused connection, until its log rate limit cuts in. A user whose connection hangs after the banner, rather than being cut off before it, has a different problem, such as the MTU black hole on a VPN path.

The address is the unit, so everyone behind it shares the clock. The colleague on the guesser's address was refused in both of the guesser's refusals, while a user on another address logged in on all 35 tries. Besides the guessers, two clients tripped it in the lab with no password involved at all. A job retrying with a key the server no longer accepts counts as failed authentication, because offering a key is an authentication attempt, and four such connections in 0.3 seconds locked the job's address out for about 21 seconds. A probe that reads the banner and hangs up was refused after 34 probes at about twice a second, for about 16 seconds; at one probe every 2 seconds it never was.

If that Connection reset by peer line is in your team chat right now, send it on WhatsApp at +254 713 403 044 with the server's sudo sshd -T output and the address the person connects from, and we will tell you whose clock it is.

fail2ban on Ubuntu 26.04 beside it

Ubuntu's fail2ban 1.1.0 package turns the sshd jail on at install, reads the systemd journal and bans with nftables, on upstream's defaults of 5 failures in 10 minutes for a 10-minute ban. It counts failure lines in the log, not connections, which is why it caught what sshd let through.

Same server and same guesser each time, with sshd restarted (which empties its penalty table) or a fresh address before each run:

What was runningGuesses per connectionWrong passwords that reached the serverWhen the address was stopped
PerSourcePenalties only114 in 76 safter the 6th or 7th miss, for 16 to 18 s, over and over
PerSourcePenalties only648 in 135 snever
fail2ban and PerSourcePenalties15 in 120 sat the 5th miss, 14.5 s in, banned for 10 minutes
fail2ban and PerSourcePenalties66 in 60 sat the 5th miss, inside the first connection
fail2ban only15 in 120 sat the 5th miss, 14.5 s in

fail2ban stopped both guessers at the same fifth line, including the one sshd never touched. The six-guess guesser was banned inside its first connection, and its sixth guess, sent a quarter of a second before the ban, was the last to arrive. Turning PerSourcePenalties off changed nothing in fail2ban's result for the one-guess guesser, because fail2ban acted at five failures and sshd's clock needed six or seven.

The bill goes to the same colleague, for longer. fail2ban's nftables rule rejects everything from a banned address to port 22, so the colleague's key logins came back Connection refused, or hung until our 5-second connect timeout, until the unban 599.6 seconds after the ban.

Start it carefully on a server that is already being guessed at. Our first start of fail2ban banned an address 0.6 seconds after the service came up, for 92 failures it found in the journal from up to nine and a half minutes before it was running. When it starts, anyone with five failures from the previous ten minutes in the part of the journal it has not read yet, your own people included, is banned as soon as it reads them, 0.6 seconds after start in our run.

Do you still need fail2ban? What we would set

Passwords off comes first, and it shrinks the whole question. Ubuntu's 26.04 cloud image already ships PasswordAuthentication no in /etc/ssh/sshd_config.d/60-cloudimg-settings.conf. Against that setting our guesser, the stock OpenSSH client, never got to send a password: 51 connections were turned down in 0.07 seconds each with Permission denied (publickey), and sshd refused the address after every 17, as connections that never attempted authentication. A server installed from the ISO with a password, one whose cloud-init user data turns password login back on, or one built by someone else can differ, and sudo sshd -T | grep -E 'passwordauthentication|kbdinteractiveauthentication' settles it: both should say no.

Keep PerSourcePenalties on, since it does slow a careless guesser and no lockout it caused in our runs lasted a minute, and put the fixed address your office connects from on its exempt list, so one person's failures stop locking out the rest. With the guesser's own address listed, all 14 of its guesses went through unrefused and the colleague got in on 19 of 19 tries. That is the trade, stated plainly: an exempt address is exempt from the protection too, so list only an address you control. On IPv6 the default counts each address on its own (PerSourceNetBlockSize 32:128), while a standard IPv6 subnet is a /64 with room to change address at will; 32:64 counts the whole /64 as one clock.

Where passwords have to stay on, for a legacy account or a tool that cannot use keys, run fail2ban as well, with the same address in ignoreip. It is the one that stopped the six-guess guesser. With both lines below in place for the guesser's own address, fail2ban logged Ignore ... by ip for each of 14 failures and banned nothing:

# /etc/ssh/sshd_config.d/20-penalties.conf
PerSourcePenaltyExemptList 203.0.113.10
# /etc/fail2ban/jail.d/50-office.local
[sshd]
ignoreip = 127.0.0.1/8 ::1 203.0.113.10

CloudSpinx builds and runs Linux servers for organizations in Kenya, and on those this choice goes into the hardening baseline the client keeps, exceptions and their reasons included. One number from the lab is worth carrying into that baseline. The longest refusal sshd handed out never went to a lone guesser, which paid 16 to 18 seconds at a time. It went to eight connections failing together from one address, about 41 seconds, which is also what several jobs retrying a revoked key at once look like.

One server, keys only, SSH reachable only from your office or a VPN: neither tool is doing much there, and you do not need us for it. For an estate, write "fail2ban or PerSourcePenalties" in "What do you need?" on the Linux scoping form, pick "Hardening and an audit", and paste into "Anything we should know?" the output of sudo sshd -T | grep -Ei 'persource|passwordauth|kbdinteractive|maxauthtries' from each server, sudo fail2ban-client status sshd wherever it runs, and the fixed addresses your staff and jobs connect from. The notes box takes 5,000 characters, about eight servers' worth; send a longer list on WhatsApp at +254 713 403 044 as a file. You get each server marked keys only or passwords on, which of the two tools to keep on it, and the exempt list and jail lines for your addresses, written as config. That costs nothing and commits you to nothing, and if we then look after the servers, it is one monthly figure.

WhatsApp