Reading the SSH auth log: what every failure line means

Where failed SSH logins are recorded on each distribution, how to decode the message variants, and how to tell a mistyped password from a botnet working a list.

8 min read

Where the log actually is

This is the first thing that trips people up, because it depends on the distribution. Debian and Ubuntu write authentication events to /var/log/auth.log. RHEL, CentOS, AlmaLinux, Rocky and Fedora use /var/log/secure. Systems that have moved fully to journald may have neither file, and everything lives in the journal.

The portable answer is journalctl, which works everywhere systemd does:

bash
# Portable: read sshd's own journal, follow live
journalctl -u ssh -f          # Debian/Ubuntu unit name
journalctl -u sshd -f         # RHEL/Fedora unit name

# Failures only, last 24 hours
journalctl -u ssh --since -24h | grep -E 'Failed|Invalid user'

# On distributions that still write the file:
grep -E 'Failed|Invalid user' /var/log/auth.log     # Debian/Ubuntu
grep -E 'Failed|Invalid user' /var/log/secure       # RHEL family

The message variants, decoded

sshd emits a small set of failure messages, and each one means something specific. Learning the five below covers almost everything you will see:

  • Failed password for <user> from <ip> port <n> ssh2 — the account exists, the password was wrong. Either a real user fumbling, or a bot that guessed a valid username.
  • Failed password for invalid user <user> from <ip> — the account does not exist. Almost always an attack: your own users know their own usernames. Usually paired with a separate "Invalid user" line.
  • Connection closed by authenticating user <user> <ip> port <n> [preauth] — the client gave up mid-authentication. Common for scanners that only wanted to fingerprint the banner, and for key-only servers rejecting password attempts.
  • Disconnected from authenticating user root <ip> port <n> [preauth] — a root login attempt against a server where root is disallowed. High volume here is the single most common signature on an exposed host.
  • Maximum authentication attempts exceeded for <user> from <ip> — the client hit MaxAuthTries in one connection. Often an agent offering many keys, but from an unfamiliar address it is a bot cycling credentials.

Telling an attack from a typo

Three signals separate them, and you want all three before acting automatically.

Rate and persistence. A person makes two or three attempts in a minute and then either gets in or calls you. A bot holds a steady rate for hours, through the night, without a pause.

Username diversity. A person tries one account: their own. A bot works down a list, and most of those accounts do not exist — which is exactly the "invalid user" signature above.

Source. Your people connect from a handful of known addresses. Attacks arrive from hosting ranges, from a different address every few hundred attempts, often from several countries in the same hour.

This groups the last day's failures by source address and shows which usernames each one tried, so all three are visible at once:

bash
journalctl -u ssh --since -24h --no-pager |
  grep -oE 'Failed password for (invalid user )?[^ ]+ from [0-9.]+' |
  awk '{ ip = $NF; user = $(NF-2); count[ip]++; users[ip] = users[ip] " " user }
       END { for (i in count) printf "%6d  %-16s %s\n", count[i], i, users[i] }' |
  sort -rn | head -15

The line you should actually alert on

Failed logins are noise. There is one message that is not, and it deserves an alert rather than a dashboard:

Accepted password for <user> from <ip> — a successful password login. If your server should be keys-only, this line means it is not, and you have a configuration problem. If passwords are permitted, this line from an address you do not recognise, right after a run of failures from the same range, is the sequence you never want to see.

Worth watching alongside it: Accepted publickey with a key fingerprint you do not recognise, and any sudo session opened for a user who should not have one. Both are cheap to alert on and both matter far more than the volume of rejections.

bash
# Every successful login in the last week, with method and source
journalctl -u ssh --since -7d --no-pager |
  grep -E 'Accepted (password|publickey|keyboard-interactive)'

From reading to blocking

Reading the log by hand is a diagnostic, not a defence — by the time you run the query, the attack has been going for a week. What changes anything is doing it continuously and turning a threshold into a firewall rule.

The naive version tails the journal, counts failures per source, and calls nft to add the address to a blocked set. It works, and then it meets the problems every such script meets: the set grows unbounded, the log rotates under load and events are missed, the unit dies quietly after an upgrade, and one bad match locks the administrator out of the only way in.

SSH Protector runs that loop as a service, with one consolidated nftables set, a whitelist that always outranks a block, bans that are restored on boot, and the decision made locally so it holds when the network does not. It reads the same stream shown above — nothing is hidden from you.

FAQ

Where are failed SSH logins logged on Linux?
On Debian and Ubuntu, /var/log/auth.log. On RHEL, CentOS, AlmaLinux, Rocky and Fedora, /var/log/secure. On systems that rely entirely on journald, neither file exists and you read the journal instead: journalctl -u ssh (or -u sshd, depending on the distribution's unit name), which works everywhere.
What does "Failed password for invalid user" mean?
It means someone tried to authenticate as an account that does not exist on the machine. Your own users know their usernames, so this line is almost always an attack — a bot working through a list of common names like root, admin, ubuntu, test, git or oracle. Volume alone is not alarming; it is the background rate on any exposed host.
Why do I see thousands of root login attempts?
Because root is the first username on every attack list, and the bots have no way to know it is disallowed on your server. With PermitRootLogin set to no or prohibit-password they cannot succeed, but they also never stop trying, since an attempt costs them nothing. The fix for the volume is blocking the sources, not the login policy.
How do I see successful SSH logins?
Search the same log for "Accepted" — Accepted publickey, Accepted password or Accepted keyboard-interactive, each followed by the username, source address and port. This is the line worth alerting on: a successful password login on a server that should be keys-only is a misconfiguration, and one from an unfamiliar address after a run of failures is an incident.

Stop reading the log by hand

The agent watches this exact stream and turns it into nftables rules, continuously. One server free forever, no card.