sshd Linux auth.log / journal Triage: low

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]

  1. 1Time — When it was logged; this format has no year or time zone
  2. 2Host — The server’s host name
  3. 3Program — sshd-session, one process per connection, and its process ID: a connection’s lines share it
  4. 4Source address — The client’s IP address
  5. 5Source port — The client’s source port; with the address it identifies one connection
  6. 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 %s message · packet.c:2024 (OpenSSH)
  • %.200s%s%s port %d connection id: preamble, address, port · packet.c:2004 (OpenSSH)
  • authctxt->valid ? "authenticating " : "invalid ", user); preamble: authenticating or invalid user · auth2.c:312 (OpenSSH)
  • %suser %s preamble 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 %s message · packet.c:2035 (OpenSSH)
  • %.200s%s%s port %d connection id: preamble, address, port · packet.c:2004 (OpenSSH)
  • authctxt->valid ? "authenticating " : "invalid ", user); preamble: authenticating or invalid user · auth2.c:312 (OpenSSH)
  • %suser %s preamble 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. So authenticating user deploy means the name matched an account sshd was willing to try, and invalid user admin means it did not — the same distinction described on the Invalid user page and in Failed password lines. 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-session runs 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.

PatternReading
No user name, [preauth], one connection per sourceBanner grab or port survey — ssh-keyscan-style probes and internet-wide scanners
No user name, [preauth], many connections from one source in secondsScripted scanning or a client retrying after a failed key exchange
invalid user <name>, many different names from one sourceName guessing — correlate with the Invalid user and Failed password lines on the same PIDs
authenticating user <real account> after password failuresA 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 errorThe client left during the banner exchange, before key exchange — port checkers and scanners
No [preauth], from an address with an Accepted line earlierAn established session dropped — network path, NAT timeout or a killed client
Any of the above followed by Accepted from the same addressStop 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 PerSourcePenalties enabled. 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. The noauth penalty (default 1s) covers clients that disconnect without attempting authentication — the manual warns to use it cautiously, as it may penalise legitimate scanning tools such as ssh-keyscan(1).
  • Keep LoginGraceTime (default 120 seconds) and MaxAuthTries (default 6) tight, so a connection that lingers or guesses is cut off sooner.
  • Remove passwords from the equation with PasswordAuthentication no (it defaults to yes), and, where PAM is enabled, KbdInteractiveAuthentication no as 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 the USER@HOST form 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

MITRE ATT&CK: T1595.001T1110.001T1021.004

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.