Last updated: 28 October 2026. Sources are listed at the end.
Any server with port 22 open to the internet will see automated password guesses. This guide shows you how to detect SSH brute force attempts on Ubuntu 24.04 with the logs you already have. Then it shows how to block repeat offenders with fail2ban.
This guide is for sysadmins, developers and small business owners who run their own Linux servers. First, you will see where sshd writes its logs on 24.04. Next come grep and awk one-liners we tested on a sample log, and a fail2ban jail that matches Ubuntu’s shipped defaults. Finally, we cover what to do if one of those guesses worked.
The short answer
- On Ubuntu 24.04, read SSH events with
journalctl -u ssh. If rsyslog is installed, the same lines also go to/var/log/auth.log. - Count
Failed passwordlines per IP and per username to see who is guessing and what they target. - The signal that matters most is an
Acceptedlogin from an IP that failed many times first. Check for it daily. - fail2ban 1.0.2 on Ubuntu 24.04 already enables the sshd jail with
backend = systemdandbanaction = nftables. Put your changes injail.local. - fail2ban slows guessing down. However, key-only authentication is what makes guessing useless.
- If an attacker got in, isolate the host, preserve the logs, rotate credentials and report to CERT-In within 6 hours.
Where SSH logs live on Ubuntu 24.04
Ubuntu 24.04 changed how SSH starts. By default, ssh.socket listens on port 22 and starts ssh.service on the first connection. The service name is still ssh, so journald queries use that unit name.
The journal is the first place to detect SSH brute force activity. Every Ubuntu 24.04 system has it, including minimal installs and containers without rsyslog.
# Last hour of SSH events
sudo journalctl -u ssh --since "1 hour ago"
# Follow live, with ISO timestamps
sudo journalctl -u ssh -f -o short-iso
# Match by program name instead of unit
sudo journalctl -t sshd --since todayIf rsyslog is installed (it is on most Ubuntu Server installs), the same lines also land in /var/log/auth.log. On 24.04 that file uses full ISO 8601 timestamps such as 2026-10-27T02:10:10.802554+05:30. Older guides assume the Oct 27 02:10:10 format, so check your parser if it was written years ago.
What brute-force attempts look like in the log
We built a 69-line sample log in our lab using Ubuntu 24.04 sshd message formats. It uses documentation IP ranges, so none of these addresses are real. Here are the lines you will see most often:
sshd[41012]: Invalid user admin from 203.0.113.5 port 44943
sshd[41012]: Failed password for invalid user admin from 203.0.113.5 port 44943 ssh2
sshd[41012]: Disconnected from invalid user admin 203.0.113.5 port 44943 [preauth]
sshd[41178]: Failed password for root from 203.0.113.5 port 50293 ssh2
sshd[41178]: Connection closed by authenticating user root 203.0.113.5 port 50293 [preauth]
sshd[41232]: error: maximum authentication attempts exceeded for root from 198.51.100.23 port 64809 ssh2 [preauth]
sshd[41362]: Accepted password for deploy from 203.0.113.5 port 58557 ssh2Invalid user means the account does not exist, so the attacker is guessing names. Failed password for root means the account exists and they are guessing its password. The maximum authentication attempts exceeded line appears when one connection uses up MaxAuthTries (6 by default). The last line is a successful password login from the same IP that failed 13 times.
How to detect SSH brute force with grep and awk
You do not need a SIEM to detect SSH brute force on one server. These one-liners work on /var/log/auth.log. If you only have the journal, pipe journalctl -u ssh --no-pager -o short-iso into the same commands instead. We tested every pipeline below on Ubuntu 24.04 in our lab against the sample log.
Step 1: Count failed attempts per IP
This command pulls the word after “from” on every failed line and counts unique IPs.
awk '/Failed password/ {for(i=1;i<=NF;i++) if($i=="from") print $(i+1)}' /var/log/auth.log \
| sort | uniq -c | sort -rnOutput from our sample:
18 198.51.100.23
13 203.0.113.5
3 198.51.100.77The loop looks for the field “from” instead of a fixed column. As a result, it works for both for root from and for invalid user admin from lines.
Step 2: Count failed attempts per username
Next, find out which accounts the attackers target. The awk skips the words “invalid user” so you get the real name.
awk '/Failed password/ {for(i=1;i<=NF;i++) if($i=="for"){u=($(i+1)=="invalid")?$(i+3):$(i+1); print u}}' \
/var/log/auth.log | sort | uniq -c | sort -rn | head 19 root
5 deploy
3 admin
1 user
1 ubuntu
1 test
1 support
1 postgres
1 oracle
1 gitLook closely at names that exist on your server, such as deploy here. Guesses against real accounts may mean the attacker knows something about you. For example, the name could come from a Git repo or a job post. It may also simply be a common default.
Step 3: List the usernames that do not exist
The Invalid user lines give you the dictionary the attacker is using. That tells you which service accounts to rename or remove.
grep -oE "Invalid user [^ ]+ from" /var/log/auth.log | awk '{print $3}' | sort | uniq -c | sort -rnStep 4: Find a successful login after many failures
The awk counts failures per IP, then prints any Accepted line from an IP with three or more earlier failures.
awk '/Failed password/ {for(i=1;i<=NF;i++) if($i=="from") f[$(i+1)]++}
/Accepted (password|publickey)/ {for(i=1;i<=NF;i++) if($i=="from") ip=$(i+1);
if (f[ip]>=3) print "ALERT:", f[ip], "failures then", $0}' /var/log/auth.logReal output from our sample:
ALERT: 13 failures then 2026-10-27T02:15:52.917941+05:30 web01 sshd[41362]: Accepted password for deploy from 203.0.113.5 port 58557 ssh2The legitimate key login from 192.0.2.10 in the same file did not trigger, because that IP had no failures. Also, list all accepted logins with their method:
grep -E "Accepted (password|publickey)" /var/log/auth.log | awk '{print $1, $5, $7, $9}'2026-10-27T02:15:31.527064+05:30 publickey bhaskar 192.0.2.10
2026-10-27T02:15:52.917941+05:30 password deploy 203.0.113.5If you have disabled passwords, any Accepted password line is a red flag on its own. Our SSH hardening checklist for Ubuntu 24.04 shows how to turn them off safely.
Step 5: Use last, lastb and lastlog
sshd also writes to the binary login records. lastb reads failed attempts from /var/log/btmp, and last reads successful sessions from /var/log/wtmp. We confirmed both commands and both files exist on Ubuntu 24.04. Our container had no real logins, so we have no output to show.
sudo lastb -n 20 # recent failed logins
last -n 20 -i # recent successful logins, with IPs
lastlog | grep -v "Never logged in" # last login per accountlastlog is still present on 24.04. However, these files are easy for an intruder with root to edit. Treat them as a quick view and the journal as your record.
Using fail2ban to detect SSH brute force and block it
fail2ban uses the same log lines to detect SSH brute force automatically, with a regex filter. When one IP crosses maxretry failures within findtime, it adds a firewall ban for bantime. Then it removes the ban when the time expires.
What Ubuntu 24.04 ships by default
Install it from the Ubuntu repos:
sudo apt install fail2banThe package ships /etc/fail2ban/jail.d/defaults-debian.conf. On our Ubuntu 24.04 lab (fail2ban 1.0.2-3ubuntu0.1) it contains:
[DEFAULT]
banaction = nftables
banaction_allports = nftables[type=allports]
backend = systemd
[sshd]
enabled = trueSo the sshd jail is on as soon as the service starts. It reads the journal (not /var/log/auth.log) and bans with nftables. Upstream jail.conf still says backend = auto and banaction = iptables-multiport, but the Debian file overrides both. We confirmed the resolved values with fail2ban-client -d, which printed ['add', 'sshd', 'systemd'] and addaction nftables.
The defaults are 5 failures (maxretry) in 10 minutes (findtime), then a 10-minute ban (bantime). That is fine for a start, but short bans let bots return quickly.
Step 6: Test the filter against your log
Before you trust any jail, run fail2ban-regex against a real log. It reads the file and reports which lines the filter matched. We tested this on Ubuntu 24.04 in our lab.
fail2ban-regex /var/log/auth.log /etc/fail2ban/filter.d/sshd.confSummary from our sample:
Failregex: 64 total
|- #) [# of hits] regular expression
| 4) [34] ^Failed (?:<F-NOFAIL>publickey</F-NOFAIL>|\S+) for ...
| 6) [10] ^[iI](?:llegal|nvalid) user <F-USER>.*?</F-USER> from <HOST>...
| 15) [3] ^maximum authentication attempts exceeded for ...
| 21) [2] ^<F-MLFFORGET><F-MLFGAINED>Accepted \w+</F-MLFGAINED>...
Lines: 69 lines, 17 ignored, 47 matched, 5 missedThe two Accepted hits are not failures. fail2ban uses them to reset its state for that connection. In addition, the 17 ignored lines are the [preauth] disconnect messages, which normal mode skips.
The sshd filter has a mode option. aggressive combines the normal, ddos and extra modes, so it also counts preauth disconnects. Run it like this:
fail2ban-regex /var/log/auth.log "/etc/fail2ban/filter.d/sshd.conf[mode=aggressive]"On our sample, aggressive mode matched 62 lines instead of 47, with only 2 ignored. Then we compared failures per IP with -o ip:
| IP | Normal mode | Aggressive mode |
|---|---|---|
| 203.0.113.5 | 20 | 32 |
| 198.51.100.23 | 21 | 21 |
| 198.51.100.77 | 6 | 9 |
Aggressive mode catches scanners that connect and drop without ever sending a password. Even so, test it first if your own monitoring tools open and close SSH connections.
Step 7: Write a jail.local
Never edit jail.conf or defaults-debian.conf, because package upgrades replace them. Instead, create /etc/fail2ban/jail.local. We validated this file in our lab with fail2ban-client -t against a copy of the config directory, and it returned OK: configuration test is successful.
[DEFAULT]
# Never ban yourself: office IP and VPN range
ignoreip = 127.0.0.1/8 ::1 192.0.2.10 192.0.2.64/27
# Repeat offenders get longer bans: 1h, 2h, 4h ... capped at 1 week
bantime.increment = true
bantime.factor = 2
bantime.maxtime = 1w
[sshd]
enabled = true
backend = systemd
mode = aggressive
maxretry = 4
findtime = 15m
bantime = 1hHere is what each setting does:
ignoreip: addresses that are never banned. Add your office, VPN and monitoring hosts first, so a typo does not lock you out.bantime.increment: each repeat ban for the same IP lasts longer, up tobantime.maxtime.backend = systemd: matches the Ubuntu default. Setbackend = autowithlogpath = /var/log/auth.logonly if you deliberately want file-based reading.maxretry,findtime,bantime: 4 failures in 15 minutes earns a one-hour first ban.
Then check and apply it:
sudo fail2ban-client -t
sudo systemctl restart fail2banWe did not restart fail2ban in our lab container, so this step is untested there. The commands are the standard ones from the fail2ban documentation.
Step 8: Manage bans with fail2ban-client
These commands come from fail2ban-client -h on fail2ban 1.0.2. We checked the syntax in our lab but did not run them against a live jail.
sudo fail2ban-client status sshd # counts and banned IP list
sudo fail2ban-client get sshd banip --with-time # bans with expiry times
sudo fail2ban-client set sshd unbanip 192.0.2.10 # remove one ban
sudo fail2ban-client set sshd banip 203.0.113.5 # ban manually
sudo fail2ban-client banned # all jails, all bans
sudo nft list table inet f2b-table # see the actual firewall setWhy fail2ban does not replace key-only authentication
fail2ban counts attempts per IP. A botnet with thousands of addresses can try a few passwords from each one and never cross maxretry. It also does nothing against a leaked password that works on the first try.
Key-only authentication removes the guessable secret entirely. Set PasswordAuthentication no and KbdInteractiveAuthentication no, and disable root login. After that, brute force becomes noise in the log. Therefore, use fail2ban to keep logs readable and slow down scans, and use keys for actual protection. The full list is in our Linux server hardening checklist.
Alternatives and extra layers
| Option | What it does | Good for |
|---|---|---|
| fail2ban | Bans IPs after log matches, per server | Single servers, simple setups |
| CrowdSec | Parses logs, shares attacker IPs across its community, blocks via “bouncers” | Fleets that want shared blocklists |
| nftables rate limit | Drops new connections over a rate per source IP | No extra daemon |
ufw limit ssh | Denies an IP after 6 connections in 30 seconds | Hosts already on ufw |
| Perimeter firewall or VPN | Port 22 reachable only from known IPs or over VPN | Anything you can restrict |
Here is a small nftables rate limit. We checked its syntax with nft -c -f on Ubuntu 24.04 but did not load it.
table inet ssh_guard {
set ssh_flood {
type ipv4_addr
flags dynamic, timeout
timeout 10m
}
chain input {
type filter hook input priority -5; policy accept;
tcp dport 22 ct state new add @ssh_flood { ip saddr limit rate over 5/minute burst 5 packets } drop
}
}The best option is to stop exposing port 22 at all. Allow it only from office IPs or a VPN at the firewall. For a broader view across many servers, ship these logs to a SIEM. Our guide to a single-server Wazuh setup for small businesses covers that.
What to do if an attacker logged in
An Accepted line from a brute-forcing IP means you should treat the server as compromised. Work through these steps in order:
- Isolate the host. Restrict its network at the firewall or cloud security group instead of shutting it down, so memory and running processes survive.
- Preserve evidence. Copy
/var/log/auth.log*, export the journal withjournalctl -u ssh -o export > ssh-journal.export, and copywtmp,btmpand the bash history of the affected account to another machine. - Check what the session did: new users,
~/.ssh/authorized_keyschanges, cron jobs, systemd units and outbound connections. - Rotate credentials. Change the account password, remove unknown keys, and rotate any secrets, API tokens or database passwords stored on that server.
- Report it. Under the CERT-In Directions of 28 April 2022, you must report unauthorised access to IT systems to CERT-In within 6 hours of noticing it.
- Rebuild from a clean image if you cannot rule out root access. Cleaning a rooted server in place is hard to verify.
Host key warnings after an incident need care too. Our post on the Tor relay SSH hijack and host key warnings explains what they mean.
Quick checklist
- ☐ Read SSH events with
journalctl -u ssh(and/var/log/auth.logif rsyslog is present) - ☐ Count failed attempts per IP and per username weekly
- ☐ Alert on any
Acceptedlogin from an IP with earlier failures - ☐ Confirm fail2ban’s sshd jail uses
backend = systemdandbanaction = nftables - ☐ Test the filter with
fail2ban-regexagainst a real log - ☐ Set
ignoreip,bantime.incrementand sensiblemaxretry/findtimeinjail.local - ☐ Disable password and root login over SSH
- ☐ Restrict port 22 to known IPs or a VPN where possible
- ☐ Write down your incident steps, including the CERT-In 6-hour report
If you want a second pair of eyes on your servers, our Linux server hardening audit checks SSH, logging and fail2ban settings against these points. You can also try the free tools in our security tools directory.
Sources
- fail2ban 1.0.2 jail.conf (upstream)
- fail2ban 1.0.2 sshd filter
- fail2ban package for Ubuntu (Launchpad, changelog)
- fail2ban-client(1), Ubuntu 24.04 manpage
- fail2ban-regex(1), Ubuntu 24.04 manpage
- sshd_config(5), Ubuntu 24.04 manpage
- Ubuntu 24.04 LTS (Noble Numbat) release notes
- ufw(8), Ubuntu 24.04 manpage
- CrowdSec documentation
- CERT-In Directions under Section 70B(6), 28 April 2022 (PDF)
Running Linux servers in production? Our Linux server hardening & security audit checks configuration, patching, SSH and logging. You can also use our free Linux server hardening checklist, or contact us.
