sshd "Connection closed by …" / "Connection reset by …": What It Means
Published
The client at the other end of the SSH connection went away — closed it cleanly, or reset it — and sshd logged the address, port and (if the client had already named an account) the user it was trying before it quit.
What it looks like
Oct 18 03:51:11 web1 sshd-session[48211]: Connection closed by 203.0.113.7 port 54321 [preauth]
- 1Time — When it was logged; this format has no year or time zone
- 2Host — The server’s host name
- 3Program — sshd-session, one process per connection, and its process ID: a connection’s lines share it
- 4Source address — The client’s IP address
- 5Source port — The client’s source port; with the address it identifies one connection
- 6[preauth] — Logged before the user authenticated
Example lines as /var/log/auth.log shows them (/var/log/secure on RHEL and Fedora):
Oct 18 03:51:11 web1 sshd-session[48211]: Connection closed by 203.0.113.7 port 54321 [preauth] Oct 18 03:51:44 web1 sshd-session[48230]: Connection closed by invalid user admin 198.51.100.22 port 40118 [preauth] Oct 18 03:52:40 web1 sshd-session[48244]: Connection closed by authenticating user deploy 198.51.100.22 port 40162 [preauth] Oct 18 04:07:02 web1 sshd-session[48502]: Connection reset by 192.0.2.44 port 51902 [preauth]
On our own server, the 30 days to 25 October 2026
Connection closed by …
94,789
lines · about 3,160 a day · 2,029 source addresses
Last seen 25 October 2026
Connection reset by …
4,370
lines · about 146 a day · 266 source addresses
Last seen 25 October 2026
LogTriage's production API server (Hetzner, Germany): port 22 open to the internet, key-only since 6 October 2026 (password and keyboard-interactive authentication off); nginx in front of the API on ports 80 and 443. Counted weekly from the sshd journal; no address ever leaves the server. More in the monthly attack report; which free blocklists already list these attackers is measured in the IP blocklist report, and you can check the addresses in your own log.
Where the line comes from
Format strings as written in the source of OpenSSH 10.2p1 — the versions this server runs. Each links to the exact line.
Connection closed by …
-
Connection closed by %smessage · packet.c:2024 (OpenSSH) -
%.200s%s%s port %dconnection id: preamble, address, port · packet.c:2004 (OpenSSH) -
authctxt->valid ? "authenticating " : "invalid ", user);preamble: authenticating or invalid user · auth2.c:312 (OpenSSH) -
%suser %spreamble format · auth2.c:311 (OpenSSH) -
mon_dispatch == mon_dispatch_postauth20 ? "postauth" : "preauth");the [preauth] tag on lines from the pre-authentication process · monitor.c:481 (OpenSSH)
Connection reset by …
-
Connection reset by %smessage · packet.c:2035 (OpenSSH) -
%.200s%s%s port %dconnection id: preamble, address, port · packet.c:2004 (OpenSSH) -
authctxt->valid ? "authenticating " : "invalid ", user);preamble: authenticating or invalid user · auth2.c:312 (OpenSSH) -
%suser %spreamble format · auth2.c:311 (OpenSSH) -
mon_dispatch == mon_dispatch_postauth20 ? "postauth" : "preauth");the [preauth] tag on lines from the pre-authentication process · monitor.c:481 (OpenSSH)
What it means
Both lines come from the same place in sshd: the handler that, in its own words, exists to “pretty-print connection-terminating errors and exit”. When the packet layer reports that the connection was closed, sshd logs Connection closed by %s; when a system call fails with errno == ECONNRESET, it logs Connection reset by %s. In both cases the remote end stopped talking and sshd gave up on that connection.
The %s is a connection identifier built from three parts — an optional logging preamble, the client’s IP address and the client’s source port, formatted as %.200s%s%s port %d. That gives you the three facts worth reading:
- The address and port. The port is the client’s source port for that single TCP connection, which is how you tie this line to the other lines sharing the same PID.
- The user preamble, when present. sshd installs it while processing the first authentication request, using
"authenticating "when the account lookup produced a usable record and"invalid "when it did not. Soauthenticating user deploymeans the name matched an account sshd was willing to try, andinvalid user adminmeans it did not — the same distinction described on theInvalid userpage and inFailed passwordlines. No preamble at all means the client never asked to authenticate as anybody. - The
[preauth]tag. sshd’s monitor appends[preauth]to messages relayed from the pre-authentication process. Without it, the line came from the session process itself: either before that child exists —sshd-sessionruns the banner exchange first, so a client that connects and hangs up before key exchange is logged untagged — or after login, from the session’s main loop.
Is it an attack?
Usually it is noise, but the variants read very differently.
| Pattern | Reading |
|---|---|
No user name, [preauth], one connection per source | Banner grab or port survey — ssh-keyscan-style probes and internet-wide scanners |
No user name, [preauth], many connections from one source in seconds | Scripted scanning or a client retrying after a failed key exchange |
invalid user <name>, many different names from one source | Name guessing — correlate with the Invalid user and Failed password lines on the same PIDs |
authenticating user <real account> after password failures | A brute-force client giving up on an account that actually exists — higher priority |
No user name and no [preauth], often after a kex_exchange_identification error | The client left during the banner exchange, before key exchange — port checkers and scanners |
No [preauth], from an address with an Accepted line earlier | An established session dropped — network path, NAT timeout or a killed client |
Any of the above followed by Accepted from the same address | Stop triaging and investigate the session |
The line itself never means anyone authenticated. It is the ending of a story told by the lines before it, so always read the connection as a whole.
How to investigate
Pivot on the source address and on the PID that each connection used.
# Last 24 hours from the journal (Debian/Ubuntu unit is ssh; RHEL/Fedora use -u sshd)
sudo journalctl -u ssh --since "24 hours ago" | grep -E "Connection (closed|reset) by"
# Busiest sources (/var/log/auth.log; on RHEL/Fedora /var/log/secure)
sudo grep -oP 'Connection (?:closed|reset) by (?:(?:authenticating|invalid) user \S+ )?\K\S+' \
/var/log/auth.log | sort | uniq -c | sort -rn | head -20
# Which account names clients were working on when they hung up
sudo grep -oP 'Connection (?:closed|reset) by (?:authenticating|invalid) user \K\S+' \
/var/log/auth.log | sort | uniq -c | sort -rn | head -20
# Lines without [preauth]: the client left during the banner exchange, or a logged-in
# session dropped — check for an earlier Accepted line from the same address
sudo grep -E "Connection (closed|reset) by" /var/log/auth.log | grep -v '\[preauth\]'
# The full story of one connection, by PID
sudo grep 'sshd-session\[48230\]' /var/log/auth.log
# Did anything from these sources actually get in?
sudo grep -E "Accepted (password|publickey)" /var/log/auth.log | tail -50
Names from the second-to-last command should be checked against /etc/passwd: an authenticating user preamble for a real service or staff account deserves more attention than a thousand invalid user closes. For an exported auth.log, the browser log analyzer groups these lines by source, and the SSH brute-force rule encodes the one-source-many-names pattern as a detection.
How to stop it
You cannot stop a remote client from hanging up, so the goal is fewer unwanted connections and less value in each one.
- Do not expose port 22 to the whole internet. Restrict it at the firewall, or put it behind a VPN or bastion. Nothing else cuts the volume as much.
- Leave
PerSourcePenaltiesenabled. The manual says penalties are on by default; a client that disconnects after one or more unsuccessful authentication attempts is refused for 5 seconds by default, accumulating up to a maximum of 10 minutes, with a minimum of 15 seconds of accrued penalty before enforcement begins. Thenoauthpenalty (default 1s) covers clients that disconnect without attempting authentication — the manual warns to use it cautiously, as it may penalise legitimate scanning tools such asssh-keyscan(1). - Keep
LoginGraceTime(default 120 seconds) andMaxAuthTries(default 6) tight, so a connection that lingers or guesses is cut off sooner. - Remove passwords from the equation with
PasswordAuthentication no(it defaults toyes), and, where PAM is enabled,KbdInteractiveAuthentication noas well — the manual notes PAM keyboard-interactive authentication usually serves an equivalent role to password authentication. - Limit who may log in with
AllowUsers; by default login is allowed for all users, and patterns may take theUSER@HOSTform with CIDR host criteria.
sudo sshd -t && sudo systemctl reload ssh
Validate the configuration before reloading, and keep an existing session open until a new one works.
Lines you will see next to it
- Invalid user … from … port … sshd rejected the login because the name the client asked for is not a valid account on this host, so no password or key was ever checked against a real user.
- … for … from … port … ssh2… sshd rejected a password authentication attempt — the line names the account that was tried, the client address and the client's source port, and marks names that are not usable accounts on this host with "invalid user".
- kex_exchange_identification(struct ssh *ssh, int timeout_ms, sshd never finished the opening banner exchange with the client — the other end went away (or sent something unreadable) before sending its identification string, so the connection ended before key exchange and before any authentication.
- Disconnected from … The client told sshd which account it wanted and then ended the connection before authentication succeeded, so the line names that account along with the source address and port.
- Received disconnect from … port …:…: … The client told the server it was closing the connection — sshd logged the client's address and port, the numeric disconnect reason the client sent and the text that came with it.
Detect it
Frequently Asked Questions
- What is the difference between "Connection closed by" and "Connection reset by"?
- They come from the same error handler in sshd's packet layer, one branch apart. "Connection closed" is logged when the packet layer reports the connection closed — the peer went away in an orderly fashion. "Connection reset" is logged when a system call failed with errno ECONNRESET, meaning the kernel saw the TCP connection torn down abruptly. Operationally both say the client left; a reset more often indicates a dropped network path, a killed client process or a NAT/firewall cutting the flow.
- Does the "[preauth]" tag mean the attacker failed?
- It means the line was written by sshd's pre-authentication process, the privilege-separated child that handles the connection before authentication completes. So yes — a connection that ends with "[preauth]" never reached an authenticated session. A line without the tag was written by the session process itself, which happens in two places — before the pre-authentication child starts, when the client hangs up during the banner exchange (the usual case for scanners, often right after a kex_exchange_identification error), or after login, when an established session's connection drops.
- Why do some lines name a user and others do not?
- sshd sets the logging preamble when it handles the first authentication request on a connection: "authenticating user <name>" if the account lookup returned something usable, "invalid user <name>" if it did not. A line with no preamble means the client never got as far as asking to authenticate as anyone — it connected, maybe exchanged a banner and keys, and hung up.
- Should I alert on these lines?
- Not on their own; on an internet-facing host they are the highest-volume sshd message there is, and most are scanners and banner grabs. Alert instead on the shape around them — one source that names many different accounts, repeated password failures from the same address immediately before the close, or any Accepted line from a source that had been producing these.
Run your own auth.log through it
Paste or drop /var/log/auth.log (or journalctl -u ssh output) into the free browser analyzer: brute force, password spraying, a successful login after failures and invalid-user sweeps are flagged with the evidence lines. Nothing is uploaded.