OpenSSH PerSourcePenalties: Block Repeated Failed SSH Connections

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_64
Client: 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
Short on time?Beta Get the gist of this 22-minute read in seconds, then ask follow-up questions

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 LoginGraceTime expires (the server’s time limit for finishing authentication)
  • Causing sshd to 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 again

A 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 to min is not enough)
  • max caps the accumulated penalty duration for a source. With the default max: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.

NOTE
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):

bash
sshd -V

On my RHEL 10 host:

output
OpenSSH_9.9p1, OpenSSL 3.5.5 27 Jan 2026

9.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:

bash
sshd -T | grep -i persource
output
persourcepenaltyexemptlist 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:permissive

If you see a persourcepenalties line, your server understands the feature. You can also open the manual and search for the same names:

bash
man sshd_config

Look 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.

NOTE
Do not decide support only from the distribution name. Check the installed OpenSSH server.

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:

bash
sshd -T | grep -E 'persourcepenalties|persourcenetblocksize|persourcemaxstartups'
output
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:permissive

How 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
IMPORTANT
A single failed authentication session does not necessarily block an IP immediately. Penalty durations add up, and blocking starts only after the total exceeds 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
WARNING
Do not run this test from the only IP you use to administer the server unless you already have console or out-of-band access. Penalties refuse new connections from that source until they expire.

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:

text
PerSourcePenalties authfail:20s min:15s max:2m
LogLevel VERBOSE

With 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:

bash
ssh -o PreferredAuthentications=password -o PubkeyAuthentication=no pspenlab@192.168.56.116

Enter 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:

bash
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:

output
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 authentication

Back on the Ubuntu client, open another SSH session to the same server while the penalty is active:

bash
ssh -o PreferredAuthentications=password -o PubkeyAuthentication=no pspenlab@192.168.56.116
output
kex_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 22

In 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:

bash
sudo journalctl -u sshd --since '5 minutes ago' | grep -i penalt
output
sshd[1257]: srclimit_penalise: ipv4: new 192.168.56.121/32 deferred penalty of 5 seconds for penalty: failed authentication

That 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.

  1. 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.
  2. From the same client IP, trigger one failed password session so an active penalty appears in the journal.
  3. Attempt a new SSH connection from that client. It should show Not allowed at this time.
  4. 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:

text
PerSourcePenaltyExemptList 192.168.56.121

Validate with sshd -t, reload, and confirm:

bash
sshd -T | grep persourcepenaltyexemptlist
output
persourcepenaltyexemptlist 192.168.56.121

Repeat 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_penalise or drop connection … penalty: lines appeared for that source
  • A second connection still reached authentication (Permission denied, not Not 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:

bash
ssh-keyscan -T 3 -t ed25519 192.168.56.116

The first scan retrieved the host key. The journal then showed an active noauth penalty:

output
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 authentication

Later 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:

text
kex_exchange_identification: banner line 0: Not allowed at this time
kex_exchange_identification: Connection closed by remote host

That 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:

bash
sshd -T | grep -E 'persourcepenalties|persourcepenaltyexemptlist|maxstartups|persourcemaxstartups'

Then read the recent daemon journal for drop or penalty lines:

bash
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-keyscan loops, failing health checks)
  • Exempt a trusted management address with PerSourcePenaltyExemptList when that address must stay reachable
  • Raise min or 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:

bash
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
EOF

What those values do:

  • authfail:15s: each failed-auth session that then disconnects adds 15 seconds toward the source penalty
  • noauth:1s: short penalty for connect-and-disconnect without auth (keep this small; ssh-keyscan can trip it)
  • grace-exceeded:10s: penalty when the client burns LoginGraceTime without finishing auth
  • crash:90s: longer penalty for crash-inducing clients
  • refuseconnection:10s: penalty when RefuseConnection rejects the client
  • min:15s: block new connections only after more than 15 seconds of accumulated penalty
  • max: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:

bash
sudo sshd -t

sshd -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:

bash
sudo systemctl reload sshd

On many Debian and Ubuntu systems the unit is named ssh:

bash
sudo systemctl reload ssh

Confirm the effective line:

bash
sshd -T | grep persourcepenalties

After the drop-in above:

output
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:permissive

max: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 ranges sshd tracks (default 65536 each here)
  • overflow / overflow6: what happens when that table fills (permissive by 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:

text
PerSourcePenalties no

Configure 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:

text
PerSourceNetBlockSize 32:128

In everyday language:

  • The first number is for IPv4. 32 means “one exact IPv4 address” (same idea as a /32 network mask)
  • The second number is for IPv6. 128 means “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:

text
PerSourceNetBlockSize 24:64

That 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.

WARNING
Broader groups often contain many legitimate users. Office NAT, VPN gateways, TCP proxies or load balancers that make many clients appear from one source address, and bastion jump hosts commonly share one or a few public addresses. Aggressive 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:

text
PerSourcePenaltyExemptList 192.0.2.10,198.51.100.0/24

That 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:

bash
sshd -T | grep persourcepenaltyexemptlist
output
persourcepenaltyexemptlist 192.0.2.10,198.51.100.0/24

You 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 PerSourceNetBlockSize from the default /128

In those setups:

  • Prefer the default PerSourceNetBlockSize 32:128 so one user does not refuse a whole office /24
  • Raise min or lower individual penalty durations if legitimate retries accumulate too fast
  • Put the shared gateway on PerSourcePenaltyExemptList only 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.

bash
sudo tee /etc/ssh/sshd_config.d/40-persource-penalties.conf >/dev/null <<'EOF'
PerSourcePenalties no
EOF

Validate the syntax first. sshd -t exits silently when the file is valid:

bash
sudo sshd -t

Reload the daemon so the disable takes effect:

bash
sudo systemctl reload sshd

Confirm the effective setting shows penalties turned off:

bash
sshd -T | grep persourcepenalties

Do 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


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.

Finished reading?Beta Get the key takeaways in seconds, then ask follow-up questions
Deepak Prasad

R&D Engineer

Founder of GoLinuxCloud with 15+ years of expertise in Linux, Python, Go, Laravel, DevOps, Kubernetes, Git, Shell scripting, OpenShift, AWS, Networking, and Security. With extensive experience, he excels across development, DevOps, networking, and security, delivering robust and efficient solutions for diverse projects.

  • Go (programming language)
  • Python (programming language)
  • DevOps
  • Computer Security
  • Cloud Computing
  • Kubernetes
  • Linux
  • Ansible (software)