Home/Security/kex_exchange_identification: read: Connection reset by peer, 5 SSH Fixes
Securitykex_exchange_identification

kex_exchange_identification: read: Connection reset by peer, 5 SSH Fixes

Getting "kex_exchange_identification: read: Connection reset by peer"? Pinpoint the cause in 5 minutes: Fail2Ban ban, MaxStartups limit, hosts.deny or sshd down, with exact commands to fix each.

kex_exchange_identification: read: Connection reset by peer, 5 SSH Fixes
Contents
  1. Distinguish SSH connection errors first
  2. 1. Record the hang point and timestamp with ssh -vvv
  3. 2. If it fails only from a specific source IP, check ban records
  4. 3. If it happens only under concurrent connections, check MaxStartups
  5. 3-1. OpenSSH 9.8+: when one source keeps being refused for a while
  6. 4. Check per-account policies and sshd status
  7. 5. Check hosts.deny only on legacy environments
  8. When every access path is blocked
  9. Verifying Changes and Preventing Recurrence

kex_exchange_identification: read: Connection reset by peer means the connection was reset during the SSH key-exchange phase. You may also see Connection closed by remote host. This string alone cannot confirm a Fail2Ban ban. Correlate the client's ssh -vvv output with server logs from the same timestamp to narrow the cause.

Distinguish SSH connection errors first

ErrorCheck first
Connection refusedWhether the SSH port is listening, address/port, connection-refusal rules
Connection timed outPath, firewalls, security groups, unresponsive server
kex_exchange_identification ... reset by peerPre-auth connection limits, blocking devices, sshd logs
Permission denied (publickey)User and key authentication after the server is reached

The client message only tells you how the connection ended. Connection closed by remote host means the peer closed the connection and the client got EOF; Connection reset by peer means it received a TCP RST. An RST can come not only from the remote host but also from a firewall, LB, or IPS on the path. The same Fail2Ban ban looks different depending on the banaction: DROP shows up as Connection timed out, the default REJECT (icmp-port-unreachable) as Connection refused, and --reject-with tcp-reset as Connection reset. Conversely, if sshd accepts the connection and then drops it by policy, you see closed.

Splitting "just me" vs. "everyone" first narrows things down a lot.

TEXT
SSH connection fails
   ├─ Also fails from another network (e.g. a phone hotspot) → sshd state, server firewall, MaxStartups, global block
   └─ Fails only from a specific source IP → Fail2Ban, PerSourcePenalties, security group source-IP rules

Key-authentication failures and connection-reset errors follow different diagnostic sequences. For a general classification of connection errors, continue with the SSH connection troubleshooting guide.

1. Record the hang point and timestamp with ssh -vvv

Bash
# run on the client: replace user, host, and port
ssh -vvv -o ConnectTimeout=10 -p 22 user@host

Record not only the last line but how far you get: Connecting to, Connection established, the remote version string, and authentication-method prompts. Verbose logs help narrow possible causes; they do not by themselves prove which component blocked the connection.

If you have a server console or an existing admin session, inspect logs from the same time.

Bash
# The service is named ssh or sshd depending on the distribution
sudo journalctl -u ssh -u sshd --since '15 minutes ago' --no-pager
sudo ss -ltnp

What you see depends on version and distribution.

  • Ubuntu 22.10 and later (including 24.04 LTS) use systemd socket activation (ssh.socket) by default. It is normal for ss -ltnp to show systemd, not sshd, holding port 22. After changing Port or ListenAddress, run sudo systemctl daemon-reload && sudo systemctl restart ssh.socket so the socket is reopened. Ubuntu: sshd socket-based activation
  • From OpenSSH 9.8, per-connection work runs in a separate sshd-session binary, so some log lines are tagged sshd-session rather than sshd. Searching only for sshd can miss them. OpenSSH 9.8 release notes
  • Check the server's OpenSSH version with ssh -V (client from the same package) or the package manager (dpkg -l openssh-server, rpm -q openssh-server).

If the server has no records at all, also check the destination, port, intermediate firewalls, and NAT path. A successful ping only confirms ICMP replies; it does not guarantee that the SSH port is working.

2. If it fails only from a specific source IP, check ban records

On a server that actually runs Fail2Ban, check the following.

Bash
sudo fail2ban-client status
sudo fail2ban-client status sshd

sshd is an example jail name. Match active jails against the source IP the server observed. In NAT or VPN environments, the client's private IP may differ from the IP the server sees.

Run the following only if a confirmed legitimate admin IP is banned in that jail and you have permission to unban it.

Bash
# 203.0.113.10 example address: replace with the IP you want to block
sudo fail2ban-client set sshd unbanip 203.0.113.10

If you do not also fix the authentication-failure cause, it can get banned again. Fail2Ban client command documentation

If your management network keeps getting banned, add your fixed management and VPN ranges to ignoreip in /etc/fail2ban/jail.local. Don't add shared NAT or broad cloud ranges.

INI
[DEFAULT]
# 203.0.113.0/24 is an example range: replace with your real management network
ignoreip = 127.0.0.1/8 ::1 203.0.113.0/24
Bash
sudo fail2ban-client reload
sudo fail2ban-client get sshd ignoreip   # confirm it took effect

3. If it happens only under concurrent connections, check MaxStartups

Bash
sudo sshd -T | grep -iE 'maxstartups|persourcemaxstartups|logingracetime'

MaxStartups limits concurrent connections that have not completed authentication. For example, if the effective setting is 10:30:100, new connections start being refused with 30% probability once there are 10 unauthenticated connections, and all are refused at 100. This is different from MaxSessions, which limits the number of completed login sessions. Check the actual effective setting for your version and distribution. OpenSSH sshd_config

First check whether automation jobs open many connections at once. After considering reducing deployment concurrency and reusing connections, adjust the limit to match server capacity and security policy. If the situation was caused by attack traffic, raising the limit alone can increase load.

3-1. OpenSSH 9.8+: when one source keeps being refused for a while

From OpenSSH 9.8, PerSourcePenalties is on by default. Source addresses that repeatedly fail authentication, or connect repeatedly without completing it, accrue penalty time; above a threshold, new connections from that address (and its PerSourceNetBlockSize range) are refused until the penalty expires. It typically shows up as a deployment tool retrying with a wrong key, or a whole office behind one NAT exit being blocked together. OpenSSH 9.8 release notes

Bash
ssh -V                                  # below 9.8 this section does not apply
sudo sshd -T | grep -iE 'persourcepenalt|persourcenetblocksize'
sudo journalctl -u ssh -u sshd --since '1 hour ago' --no-pager | grep -i penalt

How to decide: if penalty-related log lines show the source IP and access recovers on its own after a while, this feature is the likely cause. Fix what produced the repeated failures first (wrong key, expired account, aggressive retries). Exempt only ranges you fully trust, such as a management network.

TEXT
# /etc/ssh/sshd_config (or sshd_config.d/*.conf) — replace the example range with your management network
PerSourcePenaltyExemptList 192.0.2.0/24

Broad exemptions weaken brute-force protection, so do not add shared NAT or whole cloud ranges. Check option names and defaults for your server version in the sshd_config manual.

4. Check per-account policies and sshd status

Bash
sudo sshd -T | grep -iE 'allowusers|denyusers|allowgroups|denygroups'
sudo sshd -t

In environments with Match conditions, do not infer a specific connection's settings from a plain sshd -T dump alone. An administrator can pass the actual user, remote address, and so on to sshd -T -C to inspect the effective configuration. Before running it, check supported options in the local man sshd.

If you need to change the configuration, follow the order in Verifying Changes and Preventing Recurrence below (keep a session open → syntax check → reload → test from a new terminal). If the listening process went down, first fix the startup-failure cause in the journal.

5. Check hosts.deny only on legacy environments

OpenSSH removed TCP Wrappers/libwrap support in 6.7. Therefore, telling people to edit /etc/hosts.deny as a default fix is not appropriate for modern OpenSSH servers. Investigate only when you have confirmed a legacy package or a custom patch that still uses libwrap. OpenSSH 6.7 release notes

Check the current server firewall according to the distro setup: nftables, iptables, UFW, cloud security groups, and so on. Rather than disabling the entire firewall for diagnosis, inspect the required source, destination, and port rules.

When every access path is blocked

Use a separate management path: the hosting admin console, a preconfigured serial console, a recovery environment, and so on. A cloud serial console has instance-, account-, and OS-specific prerequisites; it does not always connect just because you click a button.

Common out-of-band paths and their prerequisites are below. Prepare at least one before an incident happens.

PathNeeds port 22Prerequisites
AWS SSM Session ManagerNoSSM Agent, an instance IAM role, outbound access to SSM endpoints
AWS EC2 Instance ConnectYesSecurity group allows port 22 from the Instance Connect service range (or an Endpoint)
AWS EC2 / GCP serial consoleNoEnabled at the account/project level, an OS user with a password
GCP IAP TCP forwardingYes (from the IAP range)Firewall allows 35.235.240.0/20 → port 22, IAP permissions
On-prem IPMI / iDRAC / iLONoManagement-network access, a BMC account

After recovery, record cause logs → rules you changed → whether a new connection succeeded. Choose recurrence-prevention measures based on the confirmed cause: a specific IP ban, a concurrent-connection limit, or an sshd failure.

After you have identified the block cause, see SSH hardening and Fail2Ban configuration to tidy up defensive settings.

Verifying Changes and Preventing Recurrence

Once you've found the cause and changed sshd settings, follow an order that won't lock you out while applying them.

Safe order

  1. Keep one existing SSH session open while you work (your way back if something fails).
  2. Test syntax, then apply.
Bash
sudo sshd -t && echo "config OK"
# effective values, including Include files
sudo sshd -T | grep -Ei 'maxstartups|maxsessions|persourcepenalties|logingracetime'
# reload (service name is ssh or sshd depending on distro)
sudo systemctl reload ssh || sudo systemctl reload sshd
  1. Test a new connection from another terminal before closing the old session.

Reconfirm the cause in logs

Bash
sudo journalctl -u ssh -u sshd --since "1 hour ago" | grep -Ei 'MaxStartups|penalt|refused|reset'

"past MaxStartups" points to the unauthenticated-connection limit; penalty-related lines point to PerSourcePenalties (section 3-1 above).

Prevention

  • For sources that open many connections (deployment and monitoring tools), enable connection reuse (ControlMaster/ControlPersist) to cut new connections.
  • Keep internet brute-force attempts from reaching sshd by restricting sources at the firewall or moving SSH behind a VPN or bastion.
  • Periodically collect ss -Htn state established '( sport = :22 )' | wc -l to know normal concurrency, which gives you a basis for tuning limits. Without -H (no header) the header line is counted too. The value includes authenticated sessions, so for MaxStartups (unauthenticated connections) also check the "past MaxStartups" log lines. ss manual
Verification
✦ ✦ ✦
Sources and verification

Sources and checks vary by article. Only articles with a recorded check date, scope and sources below were checked, and only within that scope. For other articles, verify against the current docs for your version before applying anything, and email us about errors. Editorial policy · email us

This article has no recorded human check (date and scope) yet.

Published · ·Updated ·
Topic-related official docs (auto-linked · not per-claim sources)Linux man pages ↗OWASP documentation ↗

Comments

Be the first to comment.