How to Detect SSH Brute-Force Attempts

An SSH brute-force attempt shows up in your log as many Failed password or Invalid user lines from a few IP addresses. You can find them with grep, get an email with SSH Monitor and stop most of them with sshd settings.

What does an SSH brute-force attempt look like in the log?

sshd writes one line per failed attempt. On Debian and Ubuntu these go to /var/log/auth.log. On Red Hat style systems they go to /var/log/secure. A password-guessing run looks like this:

Mar  3 10:00:01 web-01 sshd[100]: Failed password for root from 203.0.113.5 port 50001 ssh2
Mar  3 10:00:03 web-01 sshd[100]: Failed password for root from 203.0.113.5 port 50001 ssh2
Mar  3 10:00:05 web-01 sshd[101]: Invalid user admin from 203.0.113.9 port 50002
Mar  3 10:00:05 web-01 sshd[101]: Failed password for invalid user admin from 203.0.113.9 port 50002 ssh2

The signs are the same source address over and over, common user names such as root, admin or ubuntu, and attempts a few seconds apart. Invalid user means the name does not exist on your server.

When password logins are off, the lines change. Bots then leave lines such as Connection closed by invalid user admin 203.0.113.9 port 50002 [preauth] or Disconnected from authenticating user root ... [preauth] instead of Failed password.

How do you find failed SSH logins?

Count failed password lines in the current log:

sudo grep -c "Failed password" /var/log/auth.log

List the source addresses with the most failures:

sudo grep "Failed password" /var/log/auth.log | grep -oE "from [0-9a-fA-F:.]+" | sort | uniq -c | sort -rn | head

List the user names attackers tried that do not exist:

sudo grep "Invalid user" /var/log/auth.log | awk '{print $8}' | sort | uniq -c | sort -rn | head

On systems that log only to the systemd journal, read the sshd unit instead. The unit is ssh on Debian and Ubuntu and sshd on Red Hat style systems:

sudo journalctl -u ssh -u sshd --since "1 hour ago" | grep "Failed password"

How does SSH Monitor detect brute-force attempts?

SSH Monitor runs these searches for you on a schedule. Each run reads only the lines added since the last run, counts the failed attempts in them and, when the count reaches FAILED_THRESHOLD (5 by default), emails you the attempts grouped by source IP with the user names tried. After a sent alert it waits LOCK_DURATION seconds (3600 by default), and attempts from that wait go into the next alert. The install guide explains each setting.

What that count means:

Because it also counts Invalid user and closed-before-login lines, the alert still works on servers that allow keys only.

How do you stop SSH brute-force attacks?

The most effective step is to turn off password logins so guessing cannot work. Then cut the noise with sshd limits, a firewall and a blocker. Settings below come from the sshd_config manual.

sshd_config settings that stop password guessing
Setting in sshd_configDefaultSuggestedEffect
PasswordAuthenticationyesnoOnly key logins work, so passwords cannot be guessed
KbdInteractiveAuthenticationyesnoCloses the keyboard-interactive path that can also ask for a password
PermitRootLoginprohibit-passwordnoNobody can log in as root over SSH
MaxAuthTries63Fewer attempts per connection
AllowUsersall usersyour login usersLogins for any other name fail

Set up and test a key login before you set PasswordAuthentication no. Check the file with sudo sshd -t, then reload sshd (sudo systemctl reload ssh on Debian and Ubuntu, sshd on Red Hat style systems). Some distributions also read files in /etc/ssh/sshd_config.d/, which can override the main file.

OpenSSH 9.8 and later penalize client addresses that fail to authenticate, and this is on by default (OpenSSH 9.8 release notes). The PerSourcePenalties setting controls it.

More layers that help:

Does changing the SSH port help?

Moving SSH off port 22 cuts the number of log lines from scanners that only try the default port. It does not stop anyone who scans all ports. Treat it as a way to quiet the log, and rely on key-only logins for security.