sshd Linux auth.log / journal Triage: low

sshd "pam_unix(sshd:auth): authentication failure": What It Means

Published

PAM's pam_unix module rejected the credentials sshd asked it to check — the line names the client address in rhost= and, when it has one, the account in user=.

What it looks like

Oct 18 03:51:11 web1 sshd-session[48211]: pam_unix(sshd:auth): authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=203.0.113.7 user=root

  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. 4PAM module — Module pam_unix, PAM service sshd, auth stage
  5. 5Source address — rhost: the remote host the attempt came from
  6. 6User — The account name

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]: pam_unix(sshd:auth): authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=203.0.113.7  user=root
Oct 18 03:51:14 web1 sshd-session[48211]: pam_unix(sshd:auth): authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=203.0.113.7  user=root
Oct 18 03:52:40 web1 sshd-session[48244]: pam_unix(sshd:auth): authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=198.51.100.22  user=deploy
Oct 18 03:53:05 web1 sshd-session[48260]: pam_unix(sshd:auth): authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=198.51.100.22 

On our own server, the 30 days to 25 October 2026

authentication failure; logname=… uid=… euid=… tty=… ruser=… rhost=…

72,136

lines · about 2,405 a day · 693 source addresses

Last seen 6 October 2026

None since the server went key-only on 6 October 2026 — turning off password authentication removed this line entirely.

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 Linux-PAM 1.7.0 — the versions this server runs. Each links to the exact line.

What it means

This line is written by pam_unix, the PAM module that checks passwords against the local account database, during the authentication phase of the sshd PAM service. The prefix is assembled by PAM’s logging helper from three parts — the module name, the service name and the phase — giving pam_unix(sshd:auth). Everything after it comes from one format string in pam_unix, so the field list is always the same:

  • logname= and ruser= — taken from the module’s failure record and from the PAM_RUSER item. Under sshd there is no invoking local user, so both are normally empty; the code substitutes an empty string when the item is unset.
  • uid= / euid= — the credentials recorded for the process that called PAM, normally 0 because sshd’s privileged process runs as root.
  • tty= — the PAM_TTY item, which sshd sets to ssh.
  • rhost= — the PAM_RHOST item: the client’s IP address. This is the field you pivot on.
  • user=<name> — appended only when the module has an account name for the attempt. Note the double space before it: the format puts a space after rhost=%s, and the appended text begins with another.

The line says one thing: the credentials presented for that attempt were not accepted. It does not say the connection ended, and it does not say whether the account exists — for a name with no local record you normally get this line without user=, alongside pam_unix’s separate “check pass; user unknown” message.

Is it an attack?

On a host with port 22 open to the internet this is one of the highest-volume authentication messages there is, and most of it is untargeted password guessing. The shape around the line is what matters.

PatternReading
One user= name, two or three lines, then Accepted from the same addressSomeone mistyped their password
user=root repeatedly from one addressGeneric bot — check PermitRootLogin, whose default is prohibit-password
Many different user= names from one rhost=Name and credential guessing — see Invalid user
One real service or staff account, many sources, low rate eachSpraying against a known account; treat as targeted
Lines with no user= onlyGuesses at names that do not exist locally
Any of the above followed by an Accepted line from the same addressStop triaging and investigate the session

The SSH brute-force rule encodes the one-source-many-accounts shape as a detection.

How to investigate

Group by source, then by account, then read one connection end to end by PID.

# Last 24 hours (Debian/Ubuntu unit is ssh; on RHEL/Fedora use -u sshd)
sudo journalctl -u ssh --since "24 hours ago" | grep 'pam_unix(sshd:auth): authentication failure'

# Busiest source addresses (/var/log/auth.log; on RHEL/Fedora /var/log/secure)
sudo grep -oP 'pam_unix\(sshd:auth\): authentication failure;.*rhost=\K\S+' \
  /var/log/auth.log | sort | uniq -c | sort -rn | head -20

# Which accounts were named
sudo grep -oP 'pam_unix\(sshd:auth\): authentication failure;.*user=\K\S+' \
  /var/log/auth.log | sort | uniq -c | sort -rn | head -20

# Distinct accounts per source: many accounts from one address is spraying
sudo grep 'pam_unix(sshd:auth): authentication failure' /var/log/auth.log |
  sed -n 's/.*rhost=\([^ ]*\).*user=\([^ ]*\).*/\1 \2/p' | sort -u |
  awk '{c[$1]++} END {for (i in c) print c[i], i}' | sort -rn | head -20

# Everything one connection did, by PID
sudo grep 'sshd-session\[48244\]' /var/log/auth.log

# Did any source that produced failures get in?
sudo grep -E 'Accepted (password|publickey|keyboard-interactive)' /var/log/auth.log | tail -50

Check every name that appears in user= against /etc/passwd: a live service account being hammered is a different problem from a bot cycling through admin, test and oracle. The companion Failed password lines give you sshd’s view of the same attempts, and Connection closed usually ends the connection. For an exported auth.log, the browser log analyzer groups these lines by source address.

How to stop it

  • Take passwords out of the exchange. PasswordAuthentication no (the default is yes) and, because PAM keyboard-interactive usually serves an equivalent role, KbdInteractiveAuthentication no as well. With key-only authentication, pam_unix has nothing to reject.
  • Keep root out of reach. PermitRootLogin defaults to prohibit-password, which disables password and keyboard-interactive authentication for root.
  • Limit who may authenticate at all with AllowUsers; by default login is allowed for all users, and patterns may take the USER@HOST form with CIDR host criteria.
  • Cut off the retries. MaxAuthTries defaults to 6 attempts per connection, and LoginGraceTime disconnects after 120 seconds without a successful login.
  • Leave PerSourcePenalties enabled — penalties are on by default, and 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 once the 15-second minimum has accrued.
  • Restrict exposure. Firewall port 22 to known networks or front it with a VPN or bastion; nothing reduces the volume more.
sudo sshd -T | grep -Ei 'passwordauthentication|kbdinteractive|usepam|permitrootlogin|maxauthtries'
sudo sshd -t && sudo systemctl reload ssh

Check the effective settings, validate the configuration, and keep an existing session open until a new one works.

Lines you will see next to it

  • … 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".
  • 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.
  • Connection closed by … 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.
  • check pass; user unknown pam_unix was asked to check a password for a name it has no account record for, so the attempt failed before any password was compared — and the line deliberately leaves out both the name and the source address.
  • password check failed for user (…) PAM's password helper checked the password offered for the named account and it did not pass — a wrong password, or a name it could not resolve — and the line itself carries no address, so the sshd lines beside it are what tell you where it came from.

Detect it

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

Frequently Asked Questions

Why do I see both "Failed password" and "pam_unix(sshd:auth): authentication failure" for the same attempt?
Because two layers log the same rejection. pam_unix writes its line when the PAM authentication phase fails, and sshd writes its own line about the authentication method it was running. They normally appear within the same connection and the same PID, so treat the pair as one attempt — counting both doubles your failure numbers and makes a single guess look like two.
Why does user= appear on some lines and not others?
The format string ends with two conversions that carry the account name only when the module has one to report; lines for names that do not exist on the host typically appear without it, preceded by pam_unix's "check pass; user unknown" message. If user= is present, the name matched a local account record, which makes the attempt more interesting than a guess at a name that was never there.
I set PasswordAuthentication no, so why are these lines still being written?
sshd can still reach PAM through keyboard-interactive authentication. The manual notes that with UsePAM enabled, PAM authentication is enabled "using KbdInteractiveAuthentication and PasswordAuthentication", and that "Because PAM keyboard-interactive authentication usually serves an equivalent role to password authentication, you should disable either PasswordAuthentication or KbdInteractiveAuthentication." Set both to no if you want password-style attempts gone.
Are logname=, uid=, euid=, tty= and ruser= worth reading?
Rarely, for sshd. The module fills those fields from its own failure record and from PAM items it fetches just before logging, substituting an empty string when an item is unset — which is why logname and ruser are usually blank, tty is ssh and uid/euid are 0 for the root-owned sshd process. The investigative content is rhost= and user=.

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.