What an SSH brute-force attack actually looks like

Anatomy of the traffic that reaches every Linux server with port 22 open: who runs it, what it is really trying, and which defences change the volume rather than absorbing it.

7 min read

The attack, in plain terms

An SSH brute-force attack is not aimed at you. It is a commodity operation: someone rents a botnet, feeds it IP ranges, and has it try to log in to everything answering on port 22. The username list is short and boring — root, admin, ubuntu, test, git, oracle, postgres, and whatever service accounts are common this year.

The economics keep it running. A shell on a Linux box is worth money — for cryptomining, as a proxy, as a foothold into whatever else the machine can reach — and an attempt costs essentially nothing. Put a fresh server on a public IP and the first probe usually lands within the hour; within a week a few thousand failures a day is unremarkable.

The volume is the point. A person guessing at your password is an anecdote. A hundred thousand attempts a month from rotating addresses is a background condition, and defences designed for the first do nothing about the second.

What they are actually trying

Three distinct behaviours show up in the same log, and they call for different responses.

Root login attempts are the loudest and the least dangerous, because any correctly configured server has PermitRootLogin set to no or prohibit-password. Thousands of these a day mean the bots have not yet worked out that root is unavailable — they never do, because it costs them nothing to keep asking.

Username enumeration comes next: a run through common account names, looking for one that exists. On modern OpenSSH the timing differences that used to make this reliable are largely gone, but the bots still try, and a hit gives them something to focus on.

Credential stuffing is the one that succeeds. Username and password pairs from someone else's breach, replayed against your server. It looks like a small number of attempts, not a flood, and it works when a developer reused a password. No rate limit catches it, because there is no rate to limit.

What SSH gives you that other protocols do not

SSH has an escape hatch most exposed services lack: public-key authentication. Turn off password authentication entirely and the entire category of guessing attacks becomes impossible, not merely difficult. There is no password to guess.

That is genuinely the strongest single move available, and if you can make it, everything else in this article is secondary. It is covered in its own guide.

It does not, however, make the traffic stop. The bots do not know your configuration; they keep connecting, keep offering passwords, keep getting rejected. Each attempt still costs a TCP connection, an sshd fork, a log line and a slice of CPU, and your auth log still rotates fast enough to lose the events you actually needed. Keys-only removes the risk. It does not remove the load.

Why rate limiting alone falls short

The standard first answer is fail2ban, and it is a reasonable one — it ships in every distribution's repositories and it works. Its limits are worth understanding before you rely on it as your whole defence.

It bans single addresses. Botnets rotate through a range: ban 203.0.113.47 and .48 starts an hour later. Banning the /24 ends the run in one move, and fail2ban does not do that out of the box.

Its bans expire. The default is minutes, and the same botnet returns tonight. You can set them longer, but state lives in memory and in a file that does not survive a rebuild.

It learns nothing from anyone else. Every server discovers every attacker independently, from scratch, at the cost of absorbing the first N attempts each time.

And it fails quietly. A regex that stopped matching after a distribution upgrade, a jail disabled by a package update, a service that did not come back after a reboot — none of these announce themselves, and the failure looks exactly like peace.

What actually reduces the traffic

Only one thing changes the volume: refusing to talk to the source. Everything else negotiates with it.

That means watching the failure stream and, when a source crosses a threshold, dropping it in the firewall before sshd spends anything on it. Three properties separate a defence that holds from one that leaks:

  • Ban the subnet, not the address. Attack traffic comes from hosting ranges where neighbouring addresses belong to the same operator; legitimate users almost never share a /24 with a scanner.
  • Ban permanently, and survive reboots. The rule has to live in nftables and be restored on boot, not in a daemon's memory.
  • Whitelist yourself before anything is switched on. The most common way this goes wrong is an administrator who locks their own address out of a server they can only reach over SSH.

Doing it without maintaining the machinery

SSH Protector is that logic packaged as an agent. It reads the same journald or auth.log stream you would read, decides locally — so it keeps working if the network drops — and maintains a single consolidated nftables set containing every banned range.

On install it detects the real SSH port from the sshd configuration rather than assuming 22, and whitelists the address you are connected from before it blocks anything.

The part you cannot build alone is the shared reputation: an address that attacked another customer is already blocked before it reaches you, so you never absorb its first N attempts. One server is free forever, which is enough to watch a real attack stream on your own machine and decide from evidence rather than from an article.

FAQ

How many failed SSH logins per day are normal?
On a server not reachable from the internet, near zero. On one with port 22 open to any address, a few thousand a day is unremarkable and says nothing about you specifically — it is the background rate of internet-wide scanning. What matters is the trend and the source distribution, not the raw count.
Does disabling root login stop SSH brute-force attacks?
It stops them from succeeding against root, which is worth doing and takes one line in sshd_config. It does not reduce the attempt volume at all: the bots do not know root is unavailable and will keep trying indefinitely. Disable root login and also block the sources, or you keep paying for every attempt.
Is fail2ban enough on its own?
For a single server you check regularly, it is a reasonable defence and much better than nothing. Its practical limits are single-address bans against botnets that rotate ranges, bans that expire and do not survive a rebuild, no shared knowledge between servers, and a failure mode that is silent — a broken jail looks exactly like a quiet week.
Should I ban a whole /24 subnet for SSH attacks?
For a range that has just thrown hundreds of failed logins at you, usually yes. Attack traffic originates in hosting and VPS ranges where neighbouring addresses belong to the same operator, and residential users almost never share a /24 with a scanner. Keep your own offices and VPN exits whitelisted and the false-positive rate is close to zero.

See it against your own attack stream

One server, free forever, no card. The agent installs in a minute and whitelists your current address before it blocks anything.