| Tested on | RHEL 10.2 (Coughlan) — vm1.lab.example (192.168.56.116) as SSH server; Ubuntu 26.04 LTS — ubuntu-client.lab.example (192.168.56.121) as SSH client |
|---|---|
| Package | Server: openssh-server 9.9p1-25.el10_2.x86_64Client: OpenSSH_10.2p1 (Ubuntu) |
| Applies to | OpenSSH 9.8 and newer (feature availability follows the installed openssh-server package, not the distribution name alone) |
| Privilege | sudo or root to change sshd_config and reload sshd |
| Scope | What PerSourcePenalties does, how to confirm support, effective defaults, safe testing (including existing sessions, exemptions, and ssh-keyscan), configuration, and how this relates to MaxAuthTries, MaxStartups, and Fail2ban |
Think of it as a short timeout for a noisy IP. If the same address repeatedly fails authentication or triggers other penalized pre-authentication behavior, newer sshd versions can temporarily refuse new connections from that address. Sessions that already logged in stay up. I checked the server side on RHEL 10 (OpenSSH 9.9) and ran the failed-login tests from a separate Ubuntu 26.04 client so I did not lock out my own admin session. The same ideas apply on any Linux host whose openssh-server package is OpenSSH 9.8 or newer.
What is OpenSSH PerSourcePenalties?
PerSourcePenalties is built-in protection inside the SSH server (sshd) starting with OpenSSH 9.8. You do not install an extra package for it. On versions that support it, it is usually already on.
In plain terms: sshd keeps accumulated penalty time for each source address. Think of that accumulated time like a temporary penalty counter. Bad pre-auth behavior adds duration to the counter. When the total goes high enough, new SSH attempts from that address are refused for a while. When the time runs out, the address can connect again.
What adds penalty time includes:
- An SSH session that ends after one or more failed authentication attempts (wrong password, rejected key, and similar), then disconnects
- Connecting and leaving without attempting authentication
- Sitting in the login phase until
LoginGraceTimeexpires (the server’s time limit for finishing authentication) - Causing
sshdto crash (rare, but taken seriously)
What it does not do:
- It does not kill sessions that already authenticated successfully
- It is not Fail2ban (it does not read log files or ban other services with a firewall)
- It does not replace strong passwords, SSH keys, or
MaxAuthTries
OpenSSH project releases enable this by default. Your Linux distribution may ship an older OpenSSH, so always check the installed version (next sections) instead of guessing from the distro name alone.
How PerSourcePenalties works
Here is the path one noisy client takes:
SSH connection from 192.0.2.20
|
v
Auth fails / no auth / grace exceeded / crash
|
v
sshd adds penalty time for that source IP
|
v
Accumulated time exceeds "min"
|
v
New SSH connections from that source are refused
|
v
Penalty expires (never longer than "max")
|
v
Connections are allowed againA few rules make the feature less scary once you see them:
- Only new connections from that IP (or IP group) are refused
- A session that is already logged in keeps running
- Each penalized session ending adds some seconds; several events add up
- Blocking starts only after the total goes above
min(equal tominis not enough) maxcaps the accumulated penalty duration for a source. With the defaultmax:10m, accumulated penalties cannot extend the current penalty beyond 10 minutes at a time; continued bad behavior can result in further penalties
Example with the common defaults authfail:5 seconds and min:15 seconds: one failed authentication session that then disconnects adds 5 seconds. That is still under 15, so nothing is blocked yet. More failed sessions in a short window can push the total past 15, and then new connections from that IP start getting refused.
authfail:5s does not mean every wrong password typed inside one SSH connection adds 5 seconds. The penalty is applied when an unauthenticated SSH session ends after one or more failed authentication attempts. MaxAuthTries still controls repeated attempts inside that single connection.
Check whether your OpenSSH supports PerSourcePenalties
First ask: is my SSH server new enough? Support starts at OpenSSH 9.8. On the machine that runs sshd (the server you protect):
sshd -VOn my RHEL 10 host:
OpenSSH_9.9p1, OpenSSL 3.5.5 27 Jan 20269.9 is greater than 9.8, so this host is in range. Still confirm the setting names exist. sshd -T prints the effective config (defaults plus drop-in files), not only what you typed by hand:
sshd -T | grep -i persourcepersourcepenaltyexemptlist none
persourcemaxstartups none
persourcenetblocksize 32:128
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:permissiveIf you see a persourcepenalties line, your server understands the feature. You can also open the manual and search for the same names:
man sshd_configLook for PerSourcePenalties, PerSourcePenaltyExemptList, PerSourceNetBlockSize, and PerSourceMaxStartups.
OpenSSH 9.8 introduced the feature. RHEL 10 includes it with OpenSSH 9.9. On other distributions, check sshd -V and sshd -T because package versions and vendor backports vary.
If you later try to add the option and sshd -t reports Bad configuration option: PerSourcePenalties, the package is older than OpenSSH 9.8 (or lacks the feature). Upgrade openssh-server before the rest of this guide will help.
View the effective PerSourcePenalties defaults
Do not edit sshd_config yet. First read what sshd is already using. That avoids changing values you never needed to touch.
Narrow the dump to the PerSource lines:
sshd -T | grep -E 'persourcepenalties|persourcenetblocksize|persourcemaxstartups'persourcemaxstartups none
persourcenetblocksize 32:128
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:permissiveHow to read that line: the numbers after each keyword are seconds (so max:600 means 10 minutes). On this OpenSSH 9.9 build the important defaults are:
| Keyword | Effective value here | Plain meaning |
|---|---|---|
authfail |
5 (5s) |
Seconds added when a client disconnects after failed authentication attempt(s) in that session |
noauth |
1 (1s) |
Seconds added when a client connects and leaves without attempting authentication |
grace-exceeded |
10 (10s) |
Seconds added when login takes longer than LoginGraceTime |
crash |
90 (90s) |
Seconds added when client activity crashes sshd |
refuseconnection |
10 (10s) |
Seconds added when RefuseConnection rejects the client |
min |
15 (15s) |
Accumulated time must go above this before new connections are refused |
max |
600 (10m) |
Maximum accumulated penalty duration for a source |
min. With the defaults above, one authfail of 5 seconds is deferred (counted but not blocking yet).
Defaults can change between OpenSSH releases. Always trust sshd -T on your host over a remembered table from another version.
Most beginners can stop after these checks. The defaults already rate-limit repeated failed SSH sessions from the same source address. Change values only when colleagues behind the same office NAT get blocked together, when you want a longer ceiling, or when you need a clearer lab demo. The sections below show what the feature looks like first, then how to tune it.
Test PerSourcePenalties safely
This section is the practical proof. If you only needed “what is it / do I have it,” you can skim and return later. The steps show what success looks like in the logs when a second computer fails login against your server.
Use two hosts when you can. Keep a console or already-open admin session on the SSH server, and run the failing logins from a second client so you do not refuse your own management address.
My layout:
| Role | Host | Address | OpenSSH |
|---|---|---|---|
| SSH server | vm1.lab.example (RHEL 10.2) |
192.168.56.116 |
9.9p1 |
| SSH client | ubuntu-client.lab.example (Ubuntu 26.04) |
192.168.56.121 |
10.2p1 |
Trigger failed authentication
For a clear demonstration, temporarily raise authfail above min on the server so one failed disconnect activates a block. For testing only (not a production baseline), I used:
PerSourcePenalties authfail:20s min:15s max:2m
LogLevel VERBOSEWith stock defaults (authfail:5, min:15), you need several failed sessions before enforcement starts. That is expected, not a broken feature.
On the Ubuntu client, attempt a password login to the RHEL server that will fail. Any real account on the server works; wrong password is enough. The ssh command options below force password auth so a bad password actually reaches sshd:
ssh -o PreferredAuthentications=password -o PubkeyAuthentication=no pspenlab@192.168.56.116Enter an incorrect password and let the client disconnect. Repeat until accumulated penalty exceeds min, or use a temporary drop-in where one authfail already exceeds min.
Watch the SSH logs
On the RHEL server, follow the unit journal while the client forces the failures:
sudo journalctl -u sshd -f(On Debian or Ubuntu servers, the unit is often named ssh instead of sshd.)
After one failed password from 192.168.56.121 with the temporary authfail:20s / min:15s drop-in, the RHEL journal showed the failed password, the disconnect, the active penalty against the Ubuntu client address, and the refused follow-up connection:
sshd-session[8855]: Failed password for pspenlab from 192.168.56.121 port 48062 ssh2
sshd-session[8855]: Connection closed by authenticating user pspenlab 192.168.56.121 port 48062 [preauth]
sshd[1257]: srclimit_penalise: ipv4: new 192.168.56.121/32 active penalty of 20 seconds for penalty: failed authentication
sshd[1257]: drop connection #0 from [192.168.56.121]:48072 on [192.168.56.116]:22 penalty: failed authenticationBack on the Ubuntu client, open another SSH session to the same server while the penalty is active:
ssh -o PreferredAuthentications=password -o PubkeyAuthentication=no pspenlab@192.168.56.116kex_exchange_identification: banner line 0: Not allowed at this time
kex_exchange_identification: Connection closed by remote host
Connection closed by 192.168.56.116 port 22In this test, Not allowed at this time was the client-facing symptom of the active penalty. The server-side penalty: log confirms the cause.
With stock defaults (authfail:5, min:15) and LogLevel VERBOSE on the server, a single failed session from the Ubuntu client is only deferred. Connections from that client still succeed until more penalty time accumulates above min. Search the recent journal for the deferred line:
sudo journalctl -u sshd --since '5 minutes ago' | grep -i penaltsshd[1257]: srclimit_penalise: ipv4: new 192.168.56.121/32 deferred penalty of 5 seconds for penalty: failed authenticationThat is why one failed authentication session often seems to do nothing under the defaults.
Confirm an existing session survives the penalty
Upstream documents that penalties do not affect concurrent connections already in progress. I verified that from the same client IP.
- From
192.168.56.121, open a successful key-based login to the server and leave a long-running command writing timestamps to a file. - From the same client IP, trigger one failed password session so an active penalty appears in the journal.
- Attempt a new SSH connection from that client. It should show
Not allowed at this time. - Check that the original session’s timestamp file keeps growing.
On my run, the journal recorded an active penalty and a drop for new connections from 192.168.56.121, the new client attempt saw Not allowed at this time, and the already authenticated session from that same IP kept writing timestamps. Only new connections were refused.
Verify PerSourcePenaltyExemptList
After the penalty expires, exempt the client IP on the server:
PerSourcePenaltyExemptList 192.168.56.121Validate with sshd -t, reload, and confirm:
sshd -T | grep persourcepenaltyexemptlistpersourcepenaltyexemptlist 192.168.56.121Repeat the same failed-password test from the Ubuntu client. On my run:
- Failed authentication was still logged (
Failed password for pspenlab from 192.168.56.121) - No
srclimit_penaliseordrop connection … penalty:lines appeared for that source - A second connection still reached authentication (
Permission denied, notNot allowed at this time) - A key login from the same IP still succeeded
Exemptions skip this penalty mechanism for listed sources. Keep the list narrow.
Why ssh-keyscan and health checks can trigger penalties
noauth penalizes clients that connect and disconnect without attempting authentication. Upstream documents that this timeout should be used cautiously because legitimate tools such as ssh-keyscan can trip it. Health checks and automation that open SSH and leave without logging in can do the same.
For a short demonstration I temporarily set noauth:10s and min:5s, then ran repeated ssh-keyscan from the Ubuntu client:
ssh-keyscan -T 3 -t ed25519 192.168.56.116The first scan retrieved the host key. The journal then showed an active noauth penalty:
sshd[1257]: srclimit_penalise: ipv4: new 192.168.56.121/32 active penalty of 10 seconds for penalty: connections without attempting authentication
sshd[1257]: drop connection #0 from [192.168.56.121]:42646 on [192.168.56.116]:22 penalty: connections without attempting authenticationLater scans and a normal ssh from that client received Not allowed at this time until the penalty expired. If a trusted monitoring system legitimately makes frequent unauthenticated probes, keep noauth conservative or consider narrowly exempting that monitoring address. OpenSSH explicitly warns that noauth can catch legitimate tools such as ssh-keyscan.
Remove any temporary lab drop-in on the server when you finish. Validate with sshd -t, then reload so the host returns to the intended policy.
Fix SSH "Not allowed at this time" with PerSourcePenalties
Clients often report this banner during key exchange:
kex_exchange_identification: banner line 0: Not allowed at this time
kex_exchange_identification: Connection closed by remote hostThat banner is OpenSSH’s generic pre-auth refusal message. On OpenSSH 9.8 and newer it is used when PerSourcePenalties drops a connection, and it can also appear when MaxStartups (or related per-source startup limits) refuses a connection. Do not assume the client text alone proves a penalty.
On the SSH server, first print the effective PerSource and MaxStartups settings:
sshd -T | grep -E 'persourcepenalties|persourcepenaltyexemptlist|maxstartups|persourcemaxstartups'Then read the recent daemon journal for drop or penalty lines:
sudo journalctl -u sshd --since '10 minutes ago'Look for lines such as drop connection … penalty: or srclimit_penalise:. Those confirm PerSourcePenalties. If there is no penalty line, inspect MaxStartups / PerSourceMaxStartups and connection storms instead.
If a penalty is active:
- Wait for the penalty to expire (up to
max) - Stop the behavior that keeps adding time (wrong passwords,
ssh-keyscanloops, failing health checks) - Exempt a trusted management address with
PerSourcePenaltyExemptListwhen that address must stay reachable - Raise
minor lower individual penalty durations if legitimate clients share a NAT address
The dedicated test section above shows a RHEL 10 journal and client capture for the penalty case.
Configure PerSourcePenalties
Leave the defaults unless you have a reason to tune them. Prefer a drop-in under /etc/ssh/sshd_config.d/ when your distribution includes that directory. On RHEL 10 the main file already has Include /etc/ssh/sshd_config.d/*.conf. That keeps your change in one small file instead of editing the whole sshd_config.
For testing only, the following example makes authfail easier to observe; it is not a recommended production baseline:
sudo tee /etc/ssh/sshd_config.d/40-persource-penalties.conf >/dev/null <<'EOF'
PerSourcePenalties authfail:15s noauth:1s grace-exceeded:10s crash:90s refuseconnection:10s min:15s max:5m
EOFWhat those values do:
authfail:15s: each failed-auth session that then disconnects adds 15 seconds toward the source penaltynoauth:1s: short penalty for connect-and-disconnect without auth (keep this small;ssh-keyscancan trip it)grace-exceeded:10s: penalty when the client burnsLoginGraceTimewithout finishing authcrash:90s: longer penalty for crash-inducing clientsrefuseconnection:10s: penalty whenRefuseConnectionrejects the clientmin:15s: block new connections only after more than 15 seconds of accumulated penaltymax:5m: caps accumulated penalty duration at five minutes for a source; continued bad behavior can start further penalties after that window
OpenSSH activates a block when the accumulated penalty is greater than min, not merely equal to it. With authfail:15s and min:15s, the first failed session stays deferred; a second failed session pushes the total over the threshold.
Validate syntax before reload:
sudo sshd -tsshd -t prints nothing and exits 0 when the configuration is valid.
Reload the daemon. On RHEL, Rocky, AlmaLinux, and Fedora the unit is usually sshd:
sudo systemctl reload sshdOn many Debian and Ubuntu systems the unit is named ssh:
sudo systemctl reload sshConfirm the effective line:
sshd -T | grep persourcepenaltiesAfter the drop-in above:
persourcepenalties crash:90 authfail:15 noauth:1 grace-exceeded:10 refuseconnection:10 max:300 min:15 max-sources4:65536 max-sources6:65536 overflow:permissive overflow6:permissivemax:300 is five minutes in seconds. Configuration precedence still differs by distribution and Include order, so always verify with sshd -T rather than assuming the last file you edited won.
PerSourcePenalties options explained
Once you know the penalty-counter idea, the keyword table is just the list of events that add time:
| Option | What it penalizes |
|---|---|
authfail |
Client disconnects after one or more failed authentication attempts |
invaliduser |
Login attempts for usernames that do not exist (newer OpenSSH; not present in OpenSSH 9.9 sshd -T on this lab) |
noauth |
Client connects and disconnects without attempting authentication |
grace-exceeded |
Authentication does not finish before LoginGraceTime |
crash |
Client activity causes sshd to crash |
refuseconnection |
Connection explicitly rejected through RefuseConnection |
min |
Minimum accumulated penalty before blocking begins |
max |
Maximum accumulated penalty duration for a source |
Advanced bookkeeping keywords also appear in sshd -T (you can ignore these at first):
max-sources4/max-sources6: how many IPv4 or IPv6 source rangessshdtracks (default65536each here)overflow/overflow6: what happens when that table fills (permissiveby default: expire older penalties early so new ones can be tracked)
On OpenSSH 9.9 (RHEL 10), invaliduser is not a separate keyword yet. Invalid usernames still produce authentication failures and can contribute through authfail. Current upstream OpenSSH later added an explicit invaliduser duration; check man sshd_config on your host before you copy that keyword into a drop-in.
To turn the whole feature off (only when you have a reason), use:
PerSourcePenalties noConfigure PerSourceNetBlockSize
PerSourceNetBlockSize answers: when sshd says “this source,” does it mean one IP or a whole group of IPs?
Default upstream and on this lab:
PerSourceNetBlockSize 32:128In everyday language:
- The first number is for IPv4.
32means “one exact IPv4 address” (same idea as a/32network mask) - The second number is for IPv6.
128means “one exact IPv6 address”
So by default, Alice’s laptop and Bob’s laptop get separate counters even if they are on the same office network.
You might widen the mask on a host that only ever sees hostile scanners:
PerSourceNetBlockSize 24:64That groups many IPv4 addresses into one /24 bucket and many IPv6 addresses into one /64 bucket. One noisy address can then refuse the whole group.
PerSourceNetBlockSize values can lock out innocent colleagues who share that gateway.
Leave 32:128 unless you have a clear reason to group addresses, and re-check with sshd -T after any change.
PerSourcePenaltyExemptList
Some addresses should never get this temporary timeout: your jump host, a monitoring box, or the admin network you use when something goes wrong. Put trusted management sources on the exemption list when repeated failures or automated probes from those same addresses must never trigger PerSourcePenalties.
Example:
PerSourcePenaltyExemptList 192.0.2.10,198.51.100.0/24That line means: never apply PerSourcePenalties to 192.0.2.10, and never apply them to any address in the 198.51.100.0/24 range (256 IPv4 addresses in that block).
Useful exemptions include:
- Monitoring servers that probe SSH health
- Bastion or jump hosts that many admins share
- A trusted administration network
- Automation controllers that may retry more aggressively than interactive users
After reload, confirm the list:
sshd -T | grep persourcepenaltyexemptlistpersourcepenaltyexemptlist 192.0.2.10,198.51.100.0/24You can list single addresses or CIDR ranges. The mask must be valid for that address family. OpenSSH rejects impossible masks such as 192.0.2.0/33.
Exemptions skip this penalty feature for the listed sources. Keep the list narrow. A huge “just in case” range removes most of the protection. The test section above shows a live exemption for 192.168.56.121.
PerSourceMaxStartups vs PerSourcePenalties
These names look related, so students often mix them up. They answer different questions:
| Setting | Question it answers |
|---|---|
PerSourcePenalties |
Has this IP behaved badly enough that new logins should wait? |
PerSourceMaxStartups |
How many unfinished (not yet logged-in) SSH sessions may this IP open at once? |
MaxStartups |
Same idea as above, but for the whole server, not one IP (10:30:100 on this lab) |
PerSourceNetBlockSize |
When we say “one IP,” do we really mean one address or a group? |
They can operate together. PerSourceMaxStartups slows a flood of half-open sessions from one address. PerSourcePenalties reacts after failed or incomplete authentication. Neither replaces the other.
On this host persourcemaxstartups is none, so only the global MaxStartups rate limit applies until you set a per-source ceiling. Both paths can show the same client banner Not allowed at this time, so use the server journal to tell them apart.
PerSourcePenalties vs MaxAuthTries
Another common mix-up: “I already set MaxAuthTries, why do I need this?”
MaxAuthTries works inside one SSH connection. If someone opens one ssh session and types wrong passwords over and over in that same session, this limit stops that session after a few tries. The effective value here is 6.
PerSourcePenalties works across many connections over time. If someone opens a new ssh each time, fails once, disconnects, and repeats, MaxAuthTries alone resets for every new connection. PerSourcePenalties is the penalty counter that follows the IP between those attempts.
Keep both. Do not treat PerSourcePenalties as a drop-in replacement for MaxAuthTries. For the broader set of sshd_config auth switches, see OpenSSH authentication methods.
PerSourcePenalties vs Fail2ban
Search results often ask whether this replaces Fail2ban for SSH. Short answer: only for a narrow SSH-only case.
| Feature | PerSourcePenalties | Fail2ban |
|---|---|---|
| Built into OpenSSH | Yes | No |
| Reads log files | No | Yes |
| Applies SSH-aware penalties directly | Yes | Usually by updating a firewall rule |
| Can protect other services | No | Yes (HTTP, SMTP, and more) |
| External daemon required | No | Yes |
| Typical scope | SSH daemon only | SSH plus many other services |
Use PerSourcePenalties when you mainly want sshd itself to pause abusive SSH sources for a few minutes. Fail2ban remains relevant when you need firewall-level actions, notifications, longer or custom ban policies, or protection for services other than SSH.
If you already run Fail2ban for SSH, you can keep both, but watch for double-blocking and overlapping allow lists. Prefer one clear policy per environment rather than stacking aggressive timers on both tools without measuring.
Tune penalties for NAT, VPN, and shared networks
If many people leave your building through one public IP (office NAT or a VPN), sshd sees them as the same “source.” One person’s bad passwords can then pause everyone who shares that IP. OpenSSH’s own release notes call this out.
Treat these environments carefully:
- Corporate NAT egress
- VPN gateways
- TCP proxies or load balancers that make many clients appear from one source address
- Bastion hosts that many admins share
- IPv6 environments where you intentionally widen
PerSourceNetBlockSizefrom the default/128
In those setups:
- Prefer the default
PerSourceNetBlockSize 32:128so one user does not refuse a whole office/24 - Raise
minor lower individual penalty durations if legitimate retries accumulate too fast - Put the shared gateway on
PerSourcePenaltyExemptListonly when you trust that gateway’s own controls - Rely more on SSH key authentication and account policy than on aggressive temporary refusals
Aggressive penalties plus a wide net block size is the combination that locks out innocent users first.
Disable PerSourcePenalties
Disable only for compatibility troubleshooting, a special shared-address environment, or when another control already owns temporary SSH source blocking.
sudo tee /etc/ssh/sshd_config.d/40-persource-penalties.conf >/dev/null <<'EOF'
PerSourcePenalties no
EOFValidate the syntax first. sshd -t exits silently when the file is valid:
sudo sshd -tReload the daemon so the disable takes effect:
sudo systemctl reload sshdConfirm the effective setting shows penalties turned off:
sshd -T | grep persourcepenaltiesDo not disable by default. Upstream enables the feature because password guessing and crash-inducing clients are common on internet-facing sshd.
Troubleshooting PerSourcePenalties
| Problem | What to check |
|---|---|
Bad configuration option: PerSourcePenalties |
OpenSSH is older than 9.8, or the vendor package does not provide the feature |
| Failed login does not immediately block | Check min; penalties accumulate before enforcement. One authfail:5 with min:15 stays deferred. Also remember authfail is per failed session ending, not per wrong password inside one connection |
| Legitimate users are blocked together | Review NAT or shared IP; check PerSourceNetBlockSize; consider exemptions |
| Trusted monitoring host gets penalized | Add it to PerSourcePenaltyExemptList; watch noauth if the host uses ssh-keyscan or connect-only probes |
| Config change has no effect | Run sshd -T; check Include and drop-in order; confirm you reloaded the correct unit (sshd vs ssh) |
sshd refuses to reload |
Run sshd -t and fix syntax first |
Client sees Not allowed at this time |
Possible PerSourcePenalties refusal. Check the server journal for penalty: or srclimit_penalise; also check MaxStartups / PerSourceMaxStartups if no penalty is logged |
No srclimit_penalise lines at INFO |
Deferred penalties may need LogLevel VERBOSE; active drops still appear as drop connection … penalty: |
Summary
PerSourcePenalties is OpenSSH’s built-in short timeout for noisy client addresses. From OpenSSH 9.8 onward it is normally already enabled. It adds seconds of penalty time after failed or incomplete pre-auth sessions, then refuses new connections once that total goes above min, without dropping sessions that already logged in.
If you arrived with a specific question, the short path is: confirm the version with sshd -V, confirm the feature with sshd -T, leave the defaults unless you have a reason to change them, and use PerSourcePenaltyExemptList for trusted admin addresses. Be careful behind NAT or VPN gateways, where many people share one IP. For Not allowed at this time, trust the server journal over the client banner alone.
When you do change settings, prefer a drop-in file, validate with sshd -t, reload, and verify with sshd -T. Keep MaxAuthTries, MaxStartups / PerSourceMaxStartups, and optional Fail2ban in their own roles. For a wider hardening checklist beyond this one sshd feature, see preventing brute-force SSH attacks.
References
- OpenSSH 9.8 release notes: introduces PerSourcePenalties and enables it by default
- sshd_config(5) OpenBSD manual:
PerSourcePenalties,PerSourcePenaltyExemptList,PerSourceNetBlockSize,PerSourceMaxStartups - sshd(8) OpenBSD manual: server behavior and logging
- RHEL 10.0 release notes: OpenSSH / PerSourcePenalties: RHEL 10 ships OpenSSH 9.9 and documents sshd blocking client addresses through the new mechanism
- SSH connections refused with "Not allowed at this time" after LoginGraceTime timeout on RHEL 10: Red Hat Customer Portal KB on OpenSSH per-source penalties causing this client-side error
Frequently Asked Questions
Is PerSourcePenalties enabled by default?
Yes, upstream enables PerSourcePenalties by default starting with OpenSSH 9.8. Confirm the effective settings on your host with sshd -T | grep persourcepenalties rather than assuming a distro left the defaults untouched.
Which OpenSSH version added PerSourcePenalties?
OpenSSH 9.8 introduced PerSourcePenalties. Any server reporting OpenSSH 9.8 or newer in sshd -V is a candidate, but still verify with sshd -T because packaging and backports vary.
Does PerSourcePenalties replace Fail2ban?
No. PerSourcePenalties only affects the SSH daemon’s own per-source refusal logic. Fail2ban can still protect other services, apply firewall bans, and run custom actions. For SSH-only temporary refusals, PerSourcePenalties may be enough on its own.
Does one failed SSH login block my IP?
Usually not under the defaults. Each failed authentication session that then disconnects adds penalty time, and new connections are refused only after the accumulated time exceeds min (15 seconds on the OpenSSH 9.9 lab defaults). One authfail of 5 seconds is typically deferred, not active. Typing several wrong passwords inside one open SSH connection is mainly limited by MaxAuthTries, not by adding authfail once per typed password.
Does PerSourcePenalties disconnect existing SSH sessions?
No. An active penalty refuses new connections from that source. Sessions that already authenticated keep running. On the lab, an authenticated session from the same client IP kept writing timestamps while new connections from that IP received Not allowed at this time.
How do I whitelist a trusted IP?
Set PerSourcePenaltyExemptList to a comma-separated list of addresses or CIDR ranges, validate with sshd -t, reload sshd, and confirm with sshd -T | grep persourcepenaltyexemptlist. Keep the list narrow.
Does Ubuntu 24.04 support PerSourcePenalties?
Not with the default OpenSSH 9.6 packages. Upstream PerSourcePenalties needs OpenSSH 9.8 or newer. Ubuntu 26.04 ships a new enough OpenSSH for the feature; always confirm with sshd -V and sshd -T on the host you manage.
Does RHEL 10 support PerSourcePenalties?
Yes. RHEL 10 ships OpenSSH 9.9, which includes PerSourcePenalties and enables the defaults shown by sshd -T on a stock install.
Why do I see Not allowed at this time?
That banner is a generic pre-auth refusal. Check the server journal for drop connection lines with penalty: to confirm PerSourcePenalties. If no penalty is logged, also check MaxStartups and related startup limits, which can produce the same client message.

