kex_exchange_identification: read: Connection reset by peer does not prove an SSH password, key or account problem. When the server log also reports beginning MaxStartups throttling or dropped pre-authentication connections, OpenSSH is refusing some new handshakes because too many connections have not authenticated yet.
Existing administrator sessions can remain responsive during the same event. Authentication removes a connection from the MaxStartups population, so a healthy shell proves only that one client already crossed the gate. Keep that shell open—or secure provider-console access—before investigating. Voxfor’s first-login access checks provide the recovery baseline if keys, firewall state or console details are not already documented.
Start with one failed client’s UTC timestamp and verbose output. The exact disconnect wording varies by client and OpenSSH version, but its phase matters:
ssh -vvv -o ConnectTimeout=10 -l admin 203.0.113.10
A TCP refusal suggests no listener or an active reject. A timeout may belong to routing, firewall, provider filtering, conntrack or host pressure. A reset around key exchange can come from SSH admission policy, yet an intermediary may also reset the flow. Only server-side evidence can attribute the drop to sshd.
Preserve a narrow log window on the server:
sudo journalctl -u ssh -u sshd --since '15 minutes ago' --no-pager | grep -Ei 'MaxStartups|preauth|drop connection|throttl'
Some systems use ssh.service, others sshd.service; asking journalctl for both is read-only and one unit may simply have no entries. Log wording and detail depend on the packaged OpenSSH build and log level. Do not enable DEBUG globally during an Internet-facing incident: the upstream manual warns that debug logging can violate user privacy.
Broader admission failure needs a different owner. If fresh HTTPS, database and SSH flows fail together while established traffic survives, follow Linux conntrack capacity evidence instead of raising an SSH-only ceiling.
The current OpenSSH daemon manual defines separate controls for separate stages. Mixing them can produce a change that has no effect on the incident.
| Directive | Counted unit | What releases or ends the unit | Incident question |
|---|---|---|---|
MaxStartups |
Concurrent unauthenticated network connections, globally | Successful authentication, disconnect or LoginGraceTime expiry |
Is the daemon’s pre-auth population full? |
PerSourceMaxStartups |
Unauthenticated connections from one source address or source block | Same pre-auth lifecycle, subject to the lower applicable limit | Is one NAT, bastion or scanner consuming too much of the gate? |
MaxSessions |
Shell, login or subsystem channels inside one established SSH connection | Channel closure | Is connection multiplexing opening too many channels? |
MaxAuthTries |
Authentication attempts inside one connection | Success, disconnect or attempt limit | Is one connection trying too many credentials? |
Upstream defaults currently document MaxStartups 10:30:100. At 10 unauthenticated connections, new attempts face a 30% refusal probability; that probability rises linearly until every new attempt is refused at 100. The middle number is a percentage at the start threshold, not a connection count. Distribution packages may carry patches or older capabilities, so the running daemon remains the authority.
MaxSessions belongs after the network connection and authentication path exist. Microsoft Learn’s MaxStartups and MaxSessions comparison makes the practical split explicit: pre-auth connection drops and multiplexed channel failures are different symptoms.
Configuration files are clues; the effective daemon output is the receipt. OpenSSH normally uses the first obtained value for a keyword, and distribution include order can make an early snippet win over a later line. Capture files, version, effective values and active service before editing:
sudo sshd -V 2>&1 | head -n 1
sudo grep -RniE '^[[:space:]]*(Include|MaxStartups|PerSourceMaxStartups|PerSourceNetBlockSize|PerSourcePenalties|LoginGraceTime|MaxSessions)[[:space:]]' /etc/ssh/sshd_config /etc/ssh/sshd_config.d 2>/dev/null
sudo sshd -T | grep -E '^(maxstartups|maxsessions|logingracetime|persourcemaxstartups|persourcenetblocksize|persourcepenalties) '
systemctl is-active sshd 2>/dev/null || systemctl is-active ssh 2>/dev/null
sshd -V is not available on every older build; a missing version flag does not invalidate the remaining checks. Likewise, unsupported per-source directives may be absent from sshd -T. Never assume a directive from today’s upstream manual exists on an older LTS or enterprise package.
Pair the policy with the incident’s client timestamps, daemon messages, authentication latency and source distribution. ss -nt can show TCP endpoints, but it cannot tell you which connection still occupies an unauthenticated OpenSSH slot. Use it as corroborating network evidence, not as the MaxStartups count:
sudo ss -Hnt state established '( sport = :22 )'
sudo journalctl -u ssh -u sshd --since '15 minutes ago' --no-pager
Avoid an improvised connection flood against production. Reproduce concurrency only in staging or an approved maintenance window, with provider console access and a stop condition below the tested host’s resource boundary.
Source shape changes the safe response.
| Evidence shape | Likely interpretation | Safer next move |
|---|---|---|
| Many source addresses, low completion, repeated pre-auth drops | Scan or hostile connection pressure | Reduce public exposure or filter upstream; do not normalize attack volume into daemon capacity |
| One public address, many known employees or build workers | Shared NAT, bastion or CI fan-out | Measure legitimate peak and avoid an undersized per-source ceiling |
| Distributed known automation at one schedule boundary | Deployment or orchestration burst | Stagger jobs, reuse multiplexed connections where appropriate, or admit a measured global peak |
| SSH-only drops with explicit daemon throttling logs | MaxStartups gate is active |
Change the owning global/per-source contract after capacity review |
| Several new services fail without daemon throttling evidence | Host/network admission problem | Investigate firewall, conntrack, listener backlog, provider path and resource pressure |
Source address is not user identity. An office, carrier-grade NAT, jump host or CI egress gateway may represent dozens of authorized clients. Conversely, a distributed scan can consume global slots without any one address reaching a per-source threshold. Count successful authentications and known jobs, not just addresses.
Private administration can remove much unsolicited Internet demand. When that access model fits the workload, Tailscale SSH controls provide a separate path for identity and network policy. Do not close public SSH until the private path and provider recovery console are tested from a fresh client.
Start from the largest legitimate pre-auth burst observed across human logins, CI, SFTP and orchestration. Authentication duration matters: 20 arrivals that authenticate in one second consume a different concurrent population from 20 arrivals stalled on a slow external identity provider for 30 seconds.
Define three evidence-backed boundaries:
start: the point above normal legitimate concurrency where random early refusal may begin.rate: the initial refusal probability that protects capacity without making ordinary bursts unreliable.full: the maximum unauthenticated population the host can defend while preserving CPU, memory, file descriptors and recovery access.The upstream syntax is start:rate:full; values must satisfy the running daemon’s parser. A worked policy might read 24:30:72, but those numbers are not a recommendation. They are acceptable only if measured legitimate concurrency fits below the first threshold and a controlled test proves 72 pre-auth workers cannot starve the host.
PerSourceMaxStartups can preserve global room against one address, where supported. Before using it, inventory shared NAT and bastion fan-out. The upstream default is no per-source limit, and the lower of the per-source and global policies applies. Newer PerSourcePenalties behavior is also version-dependent; consult the release notes and local man page rather than adding it as an automatic hardening line.
Lowering LoginGraceTime frees abandoned slots sooner, but slow links, hardware-token interaction, PAM, multi-factor prompts and external identity lookups may need real time. Measure successful authentication duration before shortening it. A zero value removes the time limit and can make slot residence unbounded, so it is not an incident shortcut.
Keep the known-good SSH session open and confirm provider console access. Identify the file that owns the effective value; do not append a conflicting line blindly. Back up that file with permissions preserved, change one policy, and record the exact old value.
For an environment whose measured contract approved the worked values, a dedicated snippet could contain:
# Example only: replace with measured and approved values.
MaxStartups 24:30:72
PerSourceMaxStartups 16
LoginGraceTime 60
If PerSourceMaxStartups is unsupported or shared-NAT evidence does not justify 16, omit it. Validate the complete configuration before reload:
sudo sshd -t
sudo sshd -T | grep -E '^(maxstartups|persourcemaxstartups|logingracetime|maxsessions) '
Any syntax error is a stop condition. Reload only the unit already active on that host—commonly ssh on Debian/Ubuntu or sshd on RHEL-family systems. A reload preserves established sessions; a restart adds avoidable lockout risk. Ubuntu’s OpenSSH server guidance is the distribution reference for snippet and service behavior.
From a second terminal and the real client network, open a fresh login and verify the host key before closing the recovery session. The same fresh-client discipline used during SSH host-key rotation matters here because an old shell cannot prove new admission.
Rollback restores the previous owner file, passes sshd -t, reloads the same active unit and repeats a fresh login. If the host becomes pressured during a controlled concurrency test, stop the generator first; do not keep increasing the ceiling.
One successful login is necessary but not enough. Test a normal administrator, one representative SFTP or CI job, and the highest approved legitimate burst through the real network path. Preserve:
Acceptance means legitimate fresh clients authenticate reliably while the host retains a defended pre-auth boundary. A quiet log can be misleading if clients stopped trying, and a larger ceiling can hide the symptom while resource exposure worsens.
When the operating team cannot own log review, access-policy changes and recovery testing, the decision belongs in the broader managed-versus-unmanaged responsibility model. That link is a support-ownership decision, not a substitute for the incident evidence.
beginning MaxStartups throttling means the OpenSSH daemon reached the configured point where it begins refusing a percentage of new unauthenticated connections. Confirm the effective MaxStartups value and incident-time daemon logs before changing it.
No. MaxStartups counts concurrent unauthenticated network connections. Successful authentication removes a connection from that population; established shell, login and SFTP channels are governed by other controls.
An established session has already passed authentication and no longer consumes a MaxStartups slot. New clients still need admission through the pre-authentication gate and may be probabilistically or completely refused.
Yes. One office, bastion, carrier NAT or CI egress address can represent many authorized clients. Measure legitimate fan-out before enabling or lowering a per-source limit, and retain a tested recovery path.
Do not raise MaxStartups merely to accommodate hostile demand. Reduce exposure or filter the attack at an appropriate upstream layer first; increase capacity only when measured legitimate concurrency and host-resource tests justify it.
Usually no. MaxSessions limits shell, login or subsystem channels multiplexed inside one established connection. A reset before authentication belongs to the pre-auth or network path, where MaxStartups may be relevant.
No. A shorter LoginGraceTime releases abandoned pre-auth slots sooner, but it can reject legitimate clients using slow links, hardware tokens, PAM, multi-factor authentication or external identity services. Measure successful authentication time first.
Record the failed client timestamp, daemon throttle message, effective global and per-source policy, legitimate concurrency shape, changed owner file, syntax result, fresh human login and representative automation outcome. That evidence identifies whether OpenSSH protected a saturated pre-auth gate or merely appeared beside a different network failure. The correct repair preserves both admission reliability and a resource boundary the host can defend.