GOING FURTHER
Tested for real: I ran every command in this guide top-to-bottom on a fresh Ubuntu 24.04 virtual machine (4 CPUs, 8 GB RAM) on 13 August 2026. Versions at test time: Ubuntu 24.04.4 LTS, fail2ban 1.0.2-3ubuntu0.1, OpenSSH 9.6p1.
Found a problem? Tell me via the contact page.
Placeholders: anything in CAPS-WITH-DASHES, like
YOUR-SERVER-IP, is a placeholder for your own value. Replace it before running the command.
What you'll end up with
Any machine that gets a password wrong five times will be banned from your SSH port for ten minutes, automatically. I attacked my own test server to prove it, and this is the server's view straight afterwards:
Status for the jail: sshd
|- Filter
| |- Currently failed: 0
| |- Total failed: 5
| `- Journal matches: _SYSTEMD_UNIT=sshd.service + _COMM=sshd
`- Actions
|- Currently banned: 1
|- Total banned: 1
`- Banned IP list: ATTACKER-IP
Who this is for
GOING FURTHER. You have done the first hour guide, so you have a sudo user and UFW running. You are comfortable pasting commands and reading their output. There is one honest branch, and I flag it when it comes.
An honest note first: if you have already switched to SSH keys and turned passwords off, password-guessing bots should not be able to use password login. fail2ban is then about quieter logs and defence in depth, not survival. Worthwhile, not urgent.
Time and cost
My full test run, including deliberately attacking the machine, took 14 minutes of command time. Allow 35 minutes. Cost: nothing.
Words you'll meet
- fail2ban — a watchman service: it reads login logs, counts failures per address, and firewalls repeat offenders for a while.
- Jail — fail2ban's word for one watched service plus its rules. We only use the
sshdjail today. More at /basics. - The journal — systemd's built-in binary log, queried with
journalctl. Modern Ubuntu's primary log. - Backend — fail2ban's setting for where it reads logs: a text file, or the journal.
- Ban / unban — adding or removing a temporary firewall block on one address.
jail.local— your local settings file; package upgrades never touch it, unlike the shipped.conffiles.
Before you start
- An Ubuntu 24.04 server with a sudo user — from the first hour guide.
- A second machine (or phone hotspot) is handy for the attack test but not required. I explain the trap of attacking from your only machine in Step 4.
- Replace
YOUR-SERVER-IPandATTACKER-IPwith your real addresses.
Why "the journal way" — the 60-second version
For decades, tools like fail2ban read a text file called /var/log/auth.log. On Ubuntu 24.04, whether that file even exists depends on which flavour of image your provider used. The standard one still writes it; the "minimized" ones and most containers do not. The journal exists on all of them.
So we point fail2ban at the journal, and the whole question of "which log file?" disappears. When I tested, I also found a pleasant surprise, which is rare enough in this job to celebrate — details in Step 2.
The steps
Step 1 — install fail2ban
One package. At the time of testing it pulled in its journal-reading library (python3-systemd) automatically as a hard dependency. Naming it explicitly costs nothing and protects you on unusual images:
sudo apt install -y fail2ban python3-systemd
A passing run includes:
[...snipped...]
Setting up fail2ban (1.0.2-3ubuntu0.1) ...
Mine took 12 seconds. Check it worked:
sudo fail2ban-client version
You should see something like:
1.0.2
Note the sudo. Without it you get Permission denied to socket: /var/run/fail2ban/fail2ban.sock — that error means "you forgot sudo", not "fail2ban is broken". It caught me during testing.
Step 2 — look at what you actually got
Good news: check this before changing anything. Old guides — and there are thousands — will tell you fail2ban is broken out of the box on Ubuntu 24.04. The reason given: auth.log is missing on many images. When I tested, that was no longer true. Ubuntu's package now ships journal mode as its default. Verify it on your machine rather than trusting me or them:
cat /etc/fail2ban/jail.d/defaults-debian.conf
Your output will vary, but look for:
[DEFAULT]
banaction = nftables
banaction_allports = nftables[type=allports]
backend = systemd
[sshd]
enabled = true
The load-bearing line is backend = systemd — journal mode, straight from the package. And the jail is live:
sudo fail2ban-client status
A passing run includes:
Status
|- Number of jail: 1
`- Jail list: sshd
Check it worked: jail list shows sshd. If your file does not say backend = systemd — an older install, or a jail.local from an old guide — Step 3 fixes you regardless. "Something went wrong?" shows the exact crash I reproduced.
Step 3 — pin the journal backend in jail.local
Package defaults can change; files named .local are yours and survive upgrades. So we state our intent in jail.local, which also repairs any old-guide configuration in one stroke. This writes a three-line file:
printf '[sshd]\nenabled = true\nbackend = systemd\n' | sudo tee /etc/fail2ban/jail.local
You should see something like:
[sshd]
enabled = true
backend = systemd
Restart to apply:
sudo systemctl restart fail2ban
Check it worked:
sudo fail2ban-client status sshd
Your output will vary, but look for:
Status for the jail: sshd
|- Filter
| |- Currently failed: 0
| |- Total failed: 0
| `- Journal matches: _SYSTEMD_UNIT=sshd.service + _COMM=sshd
`- Actions
|- Currently banned: 0
|- Total banned: 0
`- Banned IP list:
The line Journal matches: _SYSTEMD_UNIT=sshd.service is the proof: fail2ban is reading the journal, not any log file. To be sure of that claim, I ran this exact setup on the test machine with rsyslog removed and no auth.log in existence at all. It worked identically.
Step 4 — attack yourself and watch the ban land
Here is the branch. Best: attack from a second machine or a phone hotspot. Acceptable: attack from your usual laptop, knowing you will ban yourself for ten minutes (the default). You must then either wait, or reach the server another way — provider console, or another device.
Do not test this from your only working SSH path unless you can wait 10 minutes.
From the attacking machine, try to log in and give the wrong password three times:
ssh YOUR-SERVER-USER@YOUR-SERVER-IP
You should see something like:
YOUR-SERVER-USER@YOUR-SERVER-IP's password:
Permission denied, please try again.
YOUR-SERVER-USER@YOUR-SERVER-IP's password:
Permission denied, please try again.
YOUR-SERVER-USER@YOUR-SERVER-IP's password:
YOUR-SERVER-USER@YOUR-SERVER-IP: Permission denied (publickey,password).
That is three failures; the default trigger is five. Run the same command again. In my test the ban landed mid-session — the third password prompt of the second attempt simply hung and the connection died. Then try once more:
ssh YOUR-SERVER-USER@YOUR-SERVER-IP
The important line is:
ssh: connect to host YOUR-SERVER-IP port 22: Connection refused
Connection refused, instantly — no password prompt, no timeout. The firewall rule fail2ban added is rejecting you before SSH ever answers.
Check it worked: on the server (console, or SSH from a non-banned machine):
sudo fail2ban-client status sshd
A passing run includes:
Status for the jail: sshd
|- Filter
| |- Currently failed: 0
| |- Total failed: 5
| `- Journal matches: _SYSTEMD_UNIT=sshd.service + _COMM=sshd
`- Actions
|- Currently banned: 1
|- Total banned: 1
`- Banned IP list: ATTACKER-IP
Currently banned: 1 with your attacking address listed. That address will stay banned for 10 minutes, then be released automatically.
Step 5 — unban yourself
Bans expire on their own, but you should know the release lever before you ever need it in anger. On the server:
sudo fail2ban-client set sshd unbanip ATTACKER-IP
The important line is:
1
Terse, but that 1 means "one address unbanned". Check it worked: sudo fail2ban-client status sshd now shows Currently banned: 0 and an empty banned list. The attacking machine can log in again — with the right password. I tested exactly that round trip.
Something went wrong?
- You see
Permission denied to socket: /var/run/fail2ban/fail2ban.sock→ it means you ranfail2ban-clientwithout sudo → add sudo. - You see
Active: failedand, insudo journalctl -u fail2ban, the lineERROR Failed during configuration: Have not found any log file for sshd jail→ it means something set the backend toauto/file mode on a machine with noauth.log— the classic 24.04 crash, usually ajail.localcopied from an old guide. I reproduced this exact failure in testing → do Step 3; the journal backend ends it. - You banned yourself from your only machine → it means a ten-minute wait, honestly → wait it out (default
bantimeis 10 minutes) or use your provider's web console and run the Step 5 unban there. - You see
Connection refusedwhen you expected a timeout → nothing is wrong; fail2ban's nftables action actively rejects, it does not silently drop like UFW does.
Undo all of this
Tested. Remove your settings file, then the package and its configuration:
sudo rm /etc/fail2ban/jail.local
sudo apt purge -y fail2ban
You should see something like:
Removing fail2ban (1.0.2-3ubuntu0.1) ...
Purging configuration files for fail2ban (1.0.2-3ubuntu0.1) ...
Check it worked: systemctl status fail2ban answers Unit fail2ban.service could not be found., and sudo nft list ruleset | grep -i f2b prints nothing — no leftover firewall rules. Both checks are from my test run, and SSH logins carried on working afterwards.
Where to go next
- SSH keys, properly — turn passwords off entirely and the door-knockers have nothing left to guess.
- Official reference: fail2ban documentation.
Last tested: 13 August 2026 on Ubuntu 24.04.4 LTS. Versions: fail2ban 1.0.2-3ubuntu0.1, python3-systemd 235-1build4, OpenSSH 9.6p1-3ubuntu13.18.
Tried it? Improved it?
Tell the forum what worked and what didn’t: real experience beats recommendations, and the best answers get folded back into this guide with credit.
Related guides
SSH keys, properly: never type a server password again
Your laptop will hold a small pair of files called an SSH key. Your server will trust it, so logging in never asks for a password — then we turn password logins off completely.
12 min read
First hour with a new Ubuntu server: locking the doors
A fully updated server, a personal account with admin rights, a firewall that blocks everything except SSH, and security updates that install themselves.
10 min read