Check my connection

Guides · Investigate an IP

SSH login attempts in my logs: am I being hacked?

You log in to your server, glance at /var/log/auth.log, and it's thousands of lines of Failed password for root from addresses all over the world. It looks like a siege. It's mostly weather.

Port 22 is the most predictable door on the internet, so automated scripts knock on it constantly, everywhere, all day. They aren't targeting you. They try a short list of user names (root, admin, oracle, ubuntu, test) with the passwords people choose most often, and move on. A failed attempt costs them nothing and tells you only that SSH is reachable. Your job is to answer two questions: did any attempt succeed, and could any future one?

Where to look

On Debian and Ubuntu with rsyslog, SSH writes to /var/log/auth.log. On Red Hat, Fedora, Rocky and Alma it's /var/log/secure. Newer Debian installs may not write that file at all and keep everything in the systemd journal. The journal works everywhere systemd runs; the service is called ssh on Debian and Ubuntu and sshd on the Red Hat family:

sudo journalctl -u ssh --since today        # Debian, Ubuntu
sudo journalctl -u sshd --since "1 hour ago" # Red Hat, Fedora, Rocky, Alma

Three counts tell you most of the story:

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

The first is the size of the noise. The second is the one that matters: every successful login. The third shows which addresses try hardest.

A worked example: one night on a small VPS

Twelve lines from a server called web1. The two addresses of people (the attacker trying admin, and the owner logging in the next morning) are documentation addresses, so no real person appears here. The rest are public infrastructure.

The example log (12 lines)
Nov  6 03:12:41 web1 sshd[24811]: Invalid user admin from 203.0.113.77 port 51022
Nov  6 03:12:43 web1 sshd[24811]: Failed password for invalid user admin from 203.0.113.77 port 51022 ssh2
Nov  6 03:12:44 web1 sshd[24811]: Connection closed by invalid user admin 203.0.113.77 port 51022 [preauth]
Nov  6 03:12:50 web1 sshd[24815]: Failed password for root from 185.220.101.1 port 40122 ssh2
Nov  6 03:12:53 web1 sshd[24815]: Failed password for root from 185.220.101.1 port 40122 ssh2
Nov  6 03:12:55 web1 sshd[24815]: Disconnected from authenticating user root 185.220.101.1 port 40122 [preauth]
Nov  6 03:14:02 web1 sshd[24830]: Invalid user oracle from 3.5.140.1 port 33412
Nov  6 03:14:04 web1 sshd[24830]: Failed password for invalid user oracle from 3.5.140.1 port 33412 ssh2
Nov  6 03:14:04 web1 sshd[24830]: Received disconnect from 3.5.140.1 port 33412:11: Bye Bye [preauth]
Nov  6 03:20:17 web1 sshd[24902]: Connection closed by 167.94.138.35 port 49212 [preauth]
Nov  6 08:41:09 web1 sshd[25510]: Accepted publickey for deploy from 198.51.100.20 port 52814 ssh2: ED25519 SHA256:k3X0exampleexampleexampleexampleexampleexa
Nov  6 08:41:09 web1 sshd[25510]: pam_unix(sshd:session): session opened for user deploy(uid=1001) by (uid=0)

Run through iplover's log checker, the five addresses sort out like this:

IP addressLinesCategoryNetwork type
203.0.113.773Private or reservedPrivate or reserved address (not checked)
185.220.101.13Tor exit nodesTor exit node
3.5.140.13Cloud and hostingHosting, cloud or data center
167.94.138.351Internet scannersNot identified
198.51.100.201Private or reservedPrivate or reserved address (not checked)

(The two documentation addresses show as reserved; in your own log, real addresses would be checked.) Now read the lines, because the categories only tell you who knocked, not whether the door opened.

Verdict: one accepted login, and it's the owner's. Nobody got in. But root still accepts passwords, so the guessing on line four could succeed one day if root's password is weak. That's what to fix.

Did anyone actually get in?

Check every Accepted line. For each one, ask: is that my user name, my key or password, and an address I recognise (home, office, VPN)? Then cross-check with the login records:

last -a | head -20     # recent successful logins, with the remote address
sudo lastb | head -20  # recent failed logins (on systems that keep them)
who                     # who is logged in right now

Some newer distributions have replaced last and lastb with other tools; if they're missing, the journal and auth.log hold the same information.

An Accepted password or Accepted publickey line you can't explain is the one result that changes everything. In that case, assume the account is compromised:

If you see this, do this

What you seeWhat it meansWhat to do
Invalid user … for names you don't haveGuessing that can't workNothing urgent. It disappears once passwords are off.
Failed password for root or another real accountGuessing against an account that existsTurn off password logins (below), or at least root's.
Connection closed by … [preauth] with no password triedA scanner reading your SSH bannerNothing.
Thousands of failures a day from a few addressesPersistent botsAdd fail2ban, or allow SSH only from your own addresses.
Accepted … lines that are all yoursNobody else got inNothing, apart from hardening.
Accepted … from an address or user you can't explainA likely break-inFollow the steps above, starting with authorized_keys.

Make guessing useless: keys only

A password can be guessed; an SSH key in practice can't. Once only keys are accepted, the bots can try forever.

First, create a key on your own computer and copy it to the server. macOS and Linux have the OpenSSH client built in. On Windows it's an optional feature, often already installed; if ssh isn't recognised, Microsoft's guide Get started with OpenSSH for Windows shows how to add it:

ssh-keygen -t ed25519
ssh-copy-id you@your-server     # on Windows, paste ~/.ssh/id_ed25519.pub into the server's ~/.ssh/authorized_keys

Then, on the server, set these in /etc/ssh/sshd_config:

PasswordAuthentication no
KbdInteractiveAuthentication no
PermitRootLogin prohibit-password

prohibit-password lets root in only with a key; use no to keep root out of SSH entirely. Before restarting, check what SSH will really use. Files in /etc/ssh/sshd_config.d/ are read too, and some cloud images ship one that turns passwords back on. For most settings the first value sshd reads wins:

sudo sshd -T | grep -Ei "passwordauthentication|kbdinteractive|permitrootlogin"

Restart SSH (sudo systemctl restart ssh, or sshd on the Red Hat family), and keep your current session open while you test a new login in a second window. If the new login fails, you can still fix things from the old one.

Cut the noise: fail2ban and firewalls

Keys make guessing harmless. fail2ban makes the log readable: it watches for repeated failures and blocks those addresses for a while in the firewall. On Debian and Ubuntu, sudo apt install fail2ban, then create /etc/fail2ban/jail.local:

[sshd]
enabled  = true
maxretry = 5
findtime = 10m
bantime  = 1h
# Without /var/log/auth.log (newer Debian), read the journal:
# backend = systemd

Restart it with sudo systemctl restart fail2ban and check it with sudo fail2ban-client status sshd.

If only you need SSH, the strongest noise filter is a firewall rule that allows port 22 from your own addresses only, or a VPN in front of the server. Moving SSH to another port also cuts the noise a lot, but it's camouflage, not protection: a scanner will still find it.

What the addresses can't tell you

The addresses in auth.log are where the connections came from: often a Tor exit, a rented cloud server or someone else's hijacked machine. They don't identify a person, and blocking them one by one won't keep up. Reporting persistent attacks to the network's abuse contact (the IP lookup shows it) is reasonable for a cloud server that hammers you for days; for a few failed logins, it isn't worth your time.

Sources

Official documents we used to check the facts in this guide.