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
| Error | Check first |
|---|---|
Connection refused | Whether the SSH port is listening, address/port, connection-refusal rules |
Connection timed out | Path, firewalls, security groups, unresponsive server |
kex_exchange_identification ... reset by peer | Pre-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.
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 rulesKey-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
# run on the client: replace user, host, and port
ssh -vvv -o ConnectTimeout=10 -p 22 user@hostRecord 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.
# 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 -ltnpWhat 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 forss -ltnpto showsystemd, notsshd, holding port 22. After changingPortorListenAddress, runsudo systemctl daemon-reload && sudo systemctl restart ssh.socketso the socket is reopened. Ubuntu: sshd socket-based activation - From OpenSSH 9.8, per-connection work runs in a separate
sshd-sessionbinary, so some log lines are taggedsshd-sessionrather thansshd. Searching only forsshdcan 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.
sudo fail2ban-client status
sudo fail2ban-client status sshdsshd 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.
# 203.0.113.10 example address: replace with the IP you want to block
sudo fail2ban-client set sshd unbanip 203.0.113.10If 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.
[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/24sudo fail2ban-client reload
sudo fail2ban-client get sshd ignoreip # confirm it took effect3. If it happens only under concurrent connections, check MaxStartups
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
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 penaltHow 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.
# /etc/ssh/sshd_config (or sshd_config.d/*.conf) — replace the example range with your management network
PerSourcePenaltyExemptList 192.0.2.0/24Broad 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
sudo sshd -T | grep -iE 'allowusers|denyusers|allowgroups|denygroups'
sudo sshd -tIn 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.
| Path | Needs port 22 | Prerequisites |
|---|---|---|
| AWS SSM Session Manager | No | SSM Agent, an instance IAM role, outbound access to SSM endpoints |
| AWS EC2 Instance Connect | Yes | Security group allows port 22 from the Instance Connect service range (or an Endpoint) |
| AWS EC2 / GCP serial console | No | Enabled at the account/project level, an OS user with a password |
| GCP IAP TCP forwarding | Yes (from the IAP range) | Firewall allows 35.235.240.0/20 → port 22, IAP permissions |
| On-prem IPMI / iDRAC / iLO | No | Management-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
- Keep one existing SSH session open while you work (your way back if something fails).
- Test syntax, then apply.
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- Test a new connection from another terminal before closing the old session.
Reconfirm the cause in logs
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 -lto 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 forMaxStartups(unauthenticated connections) also check the "past MaxStartups" log lines. ss manual
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.
Comments
Be the first to comment.