SSD Nodes Learn
Guides Matt ConnorBy Matt Connor

Fail2ban on Ubuntu 24.04: stop SSH attacks

Install and configure Fail2ban on Ubuntu 24.04 to ban brute-force SSH attackers at the firewall: verify the stock jail, tune bans, recover from lockout.

What Fail2ban actually does

Fail2ban is a log-reading daemon. It watches your SSH authentication messages and, after a handful of failures from one address inside a short window, runs a firewall command that blocks that address for a while. That is the whole idea. It is about thirty lines of config in one file, and on Ubuntu 24.04 the install is a single apt command that leaves you protected before you have edited anything.

Be clear about what it is and is not. Fail2ban does not authenticate anyone, does not encrypt anything, and does not stop a single determined login attempt — only repeated ones from the same source. It is a noise filter and a rate limiter, not a lock. Its job is to make the constant background scanning of port 22 stop wasting your CPU, your bandwidth, and your log space, and to slow any attacker who has to come from one address at a time.

What Fail2ban does not replace

Fail2ban is the third layer, not the first. If your server still accepts SSH passwords, a botnet spread across thousands of addresses can keep guessing, because each address stays under your ban threshold and never trips it. The real defence against that is key-only authentication, which makes password guessing impossible no matter how many attempts anyone makes. Fail2ban on top of key-only auth does two useful things: it trims the brute-force noise out of your logs, and it evicts scanners early so they stop hammering the port. Treat it as defence in depth. It sits behind key authentication and behind a firewall, never in front of them.

Prerequisites, and the Ubuntu 24.04 reality

You need a VPS running Ubuntu 24.04 with root or sudo, and SSH already working — ideally on key authentication. Fail2ban is frugal: a few tens of megabytes of RAM, no tuning of limits required.

Now the part every older guide gets wrong. For years the standard advice was "install Fail2ban, then add backend = systemd, because Ubuntu stopped writing /var/log/auth.log." That advice describes a real change — modern server and cloud images ship without rsyslog, so SSH logs only to the systemd journal and that text file is gone — but on Ubuntu 24.04 the Fail2ban package already accounts for it. The package drops in /etc/fail2ban/jail.d/defaults-debian.conf, and that file, not the upstream defaults, is what your server actually runs:

[DEFAULT]
banaction = nftables
banaction_allports = nftables[type=allports]
backend = systemd

[sshd]
enabled = true

Read that carefully, because it settles two questions before you touch anything. backend = systemd means the SSH jail reads the journal, so the missing auth.log does not matter. banaction = nftables means bans are enforced through nftables, which is the firewall Ubuntu 24.04 actually uses, rather than legacy iptables. And [sshd] enabled = true means the jail is on from first boot. The upshot: a stock apt install fail2ban on Ubuntu 24.04 bans SSH brute-force out of the box. Most of your work is confirming that, tuning the policy, and making sure you cannot lock yourself out.

The old auth.log trap still bites in three situations, and it is worth recognising them: you installed Fail2ban with pip instead of apt, so there is no defaults-debian.conf; you are inside an unprivileged container with no systemd journal to read; or you followed an old tutorial and pasted backend = auto into your own jail.local, overriding the working default. The failure-modes section shows exactly what each looks like.

Step 1: Install and confirm it is already banning

sudo apt update
sudo apt install -y fail2ban

Ubuntu 24.04 ships Fail2ban 1.0.2, and the package pulls in python3-systemd as a hard dependency, so the journal backend has everything it needs. The service enables and starts itself:

sudo systemctl status fail2ban

You want active (running). Then look at the jail that is already doing its job:

sudo fail2ban-client status sshd

On a public VPS that has been reachable for even a few minutes, you will often already see failures counted and addresses banned — the internet scans port 22 continuously. That is the proof the stock config works. From here you are refining it, not building it from nothing.

Step 2: Edit jail.local, never jail.conf

Fail2ban keeps its upstream defaults in /etc/fail2ban/jail.conf. Do not edit that file. Every apt upgrade of the package can replace it, and your changes vanish with no warning. Fail2ban reads files in a fixed order — jail.conf first, then everything in jail.d/, then jail.local — and the last value wins. The .local file is yours, and package upgrades never touch it. The same rule applies to filters, where a *.local file overrides the shipped filter.d/*.conf.

So you write a small jail.local that overrides only the handful of settings you care about, and leave both jail.conf and the packaged jail.d/defaults-debian.conf untouched as reference.

Step 3: Write /etc/fail2ban/jail.local

sudo nano /etc/fail2ban/jail.local

Put this in, changing the address on the ignoreip line to your own public IP:

[DEFAULT]
# Ubuntu 24.04 already sets these two in jail.d/defaults-debian.conf.
# Pinning them here documents the dependency and survives if that
# file is ever removed or changed by an upgrade.
backend   = systemd
banaction = nftables

# Ban for one hour ...
bantime  = 1h
# ... if an address fails ...
maxretry = 5
# ... 5 times within 10 minutes.
findtime = 10m

# Never ban these. PUT YOUR OWN IP HERE.
ignoreip = 127.0.0.1/8 ::1 203.0.113.24

# Longer bans for repeat offenders: 1h, 2h, 4h ... up to a week.
bantime.increment = true
bantime.factor    = 2
bantime.maxtime   = 1w

[sshd]
enabled = true

Every line earns its place:

  • bantime, findtime, maxretry are the policy. The shipped default bantime is only ten minutes; an hour is a saner floor. Five failures from one address inside ten minutes earns the ban. Real people mistype a password once or twice; five failures in ten minutes is a script.
  • ignoreip is your safety belt. Put the public address you connect from here so Fail2ban can never lock you out of your own server. A home connection with a changing IP is a reason to prefer the VPN approach at the end, not a reason to skip this line.
  • bantime.increment = true makes each repeat ban longer than the last — one hour, then two, then four — up to bantime.maxtime. Addresses that keep coming back get progressively locked out.

Find the address to whitelist from the machine you SSH from, not from the server:

curl -s ifconfig.me

Step 4: Restart and verify it is reading the journal

sudo fail2ban-client -t
sudo systemctl restart fail2ban
sudo fail2ban-client status sshd

The -t runs a config test first, so a typo in jail.local fails loudly here instead of leaving the service dead. A healthy jail status looks like this:

Status for the jail: sshd
|- Filter
|  |- Currently failed: 0
|  |- Total failed:     14
|  `- Journal matches:  _SYSTEMD_UNIT=sshd.service + _COMM=sshd
`- Actions
   |- Currently banned: 1
   |- Total banned:     3
   `- Banned IP list:   203.0.113.66

The number that proves Fail2ban is genuinely reading your logins is Total failed. If it is above zero, or climbs when you deliberately fail a login from another machine, the journal is being read and you are done. If it stays at 0 no matter how many times you fail — and you are sure you are not testing from the address in ignoreip — jump to the failure modes below.

Notice the Journal matches line still names sshd.service. On Ubuntu the SSH unit is actually ssh.service, but the shipped filter also matches on _COMM=sshd, and OpenSSH on 24.04 logs its failures from a process named sshd, so the match works. That detail only matters if you are on a newer OpenSSH (9.8 or later, where the per-connection worker is sshd-session); the failure modes cover that case.

Step 5: Watch a real ban land, or force one to test

Real bans arrive on their own within minutes on any public VPS. To watch one, tail the log:

sudo tail -f /var/log/fail2ban.log

A ban looks like this:

2026-07-15 10:31:40,502 fail2ban.filter  [812]: INFO    [sshd] Found 203.0.113.66 - 2026-07-15 10:31:40
2026-07-15 10:31:44,118 fail2ban.actions [812]: NOTICE  [sshd] Ban 203.0.113.66

To prove the machinery end to end without waiting, ban a documentation address by hand — never your own:

sudo fail2ban-client set sshd banip 203.0.113.66

It prints 1, and the address appears under Banned IP list in fail2ban-client status sshd. Now confirm the block really exists in the firewall. On Ubuntu 24.04 that is nftables, not iptables:

sudo nft list table inet f2b-table

You will see a set named addr-set-sshd holding 203.0.113.66, and a chain f2b-chain that rejects any source in that set. If fail2ban-client says an address is banned but nothing appears in nft list, your ban action does not match your firewall — see the nftables/iptables note in the failure modes.

Step 6: Unban yourself, and recover if you are locked out

If you banned an address you should not have — your own — remove it:

sudo fail2ban-client set sshd unbanip 203.0.113.66

It returns 1 on success. To clear every ban across every jail:

sudo fail2ban-client unban --all

Do not count on an already-open SSH session to save you: the nftables ban rejects every packet from the banned address to port 22 — established connections included — so an existing session freezes the moment the ban lands. If you ban yourself and have no ignoreip entry, you are locked out until the ban expires — recover through your provider's web console (VNC or serial), which does not go through SSH, and either wait out bantime or run the unban command there.

Step 7: Make bans persist and escalate

Fail2ban keeps active bans in a small SQLite database at /var/lib/fail2ban/fail2ban.sqlite3, so they survive a service restart or a reboot; you do not lose them. The bantime.increment lines you already added turn each repeat offender into an escalating problem for themselves — roughly doubling from one hour toward one week.

For a system-wide "three strikes" policy on top of that, Fail2ban ships a recidive jail that watches its own /var/log/fail2ban.log and hands long bans to any address that has been banned repeatedly across all jails. Because your [DEFAULT] now uses the systemd backend, pin this jail back to the log file it is designed to read:

[recidive]
enabled  = true
backend  = auto
logpath  = /var/log/fail2ban.log
bantime  = 1w
findtime = 1d
maxretry = 5

backend = auto with the explicit logpath keeps recidive reading the plain fail2ban.log, which is where the Ban lines it counts actually appear — the systemd default you set globally would point it at the journal, where they do not.

Step 8: Pair it with key-only SSH, and better still a VPN

Fail2ban earns its keep only alongside key authentication. In a drop-in file under /etc/ssh/sshd_config.d/ — say /etc/ssh/sshd_config.d/99-hardening.conf — set:

PasswordAuthentication no
KbdInteractiveAuthentication no

Then sudo systemctl restart ssh. With passwords off, brute force cannot succeed at all; Fail2ban then exists to cut the log noise and evict scanners early. Stronger still is to keep SSH off the public internet entirely: put SSH behind a self-hosted WireGuard VPN and firewall port 22 so it only answers on the tunnel. Nobody can brute-force a port they cannot reach, and Fail2ban becomes a backstop rather than a front line.

Fail2ban is not only for SSH. Any service that logs failed logins can get a jail — a mail server, an nginx site, or a self-hosted Vaultwarden password manager whose web login you would rather not leave open to credential stuffing. Once a web app sits behind an nginx site with a Let's Encrypt certificate, point a Fail2ban filter at its access log the same way the SSH jail points at the journal.

Failure modes, with the exact strings you will see

"Have not found any log file for sshd jail", and Fail2ban will not start. This is the old auth.log problem, and on Ubuntu 24.04 you only hit it if something has overridden the packaged default — a pip install with no defaults-debian.conf, a container with no journal, or a stray backend = auto you pasted into jail.local. On a file backend with no /var/log/auth.log, the sshd jail cannot find its log and the whole daemon aborts. fail2ban.log shows:

ERROR   Failed during configuration: Have not found any log file for sshd jail

Because that error is fatal, the service never comes up, and fail2ban-client status then reports the downstream symptom:

ERROR  Failed to access socket path: /var/run/fail2ban/fail2ban.sock. Is fail2ban running?

That "socket path" line does not mean Fail2ban is broken — it means it never started because one jail could not find its log. Setting backend = systemd in [DEFAULT], which the Ubuntu package already does for you, fixes both messages at once.

Jail is active but Total failed never moves. The daemon is running and the journal is being read, yet real failures pile up in journalctl -u ssh while the counter sits at 0. First rule out the obvious: you are testing from an address listed in ignoreip, so your own failures are exempt by design. If that is not it, you are on an OpenSSH build where the per-connection worker is sshd-session (9.8 and later), whose journal _COMM is sshd-session, not sshd, so the shipped match misses it. Widen the match in the [sshd] block:

[sshd]
enabled      = true
backend      = systemd
journalmatch = _SYSTEMD_UNIT=ssh.service + _COMM=sshd + _COMM=sshd-session

Restart, fail a login on purpose from an address not in ignoreip, and confirm Total failed finally climbs.

You banned yourself: Connection refused. You left your own address out of ignoreip, tested a few bad logins, and now:

ssh: connect to host 203.0.113.10 port 22: Connection refused

The refusal, rather than a silent timeout, is the nftables action's default reject verdict doing its job — on you. Fix it as in Step 6: unban from an already-open session, or from the provider console. Then add your address to ignoreip so it cannot happen again.

Fail2ban says an address is banned, but it can still connect. The counter in status sshd rises, yet the address still reaches port 22. This is a ban-action-versus-firewall mismatch, and on Ubuntu 24.04 it almost always means you overrode the working banaction = nftables with banaction = iptables-multiport copied from an older guide, on a box with no iptables layer. fail2ban.log shows:

fail2ban.actions [812]: ERROR  Failed to execute ban jail 'sshd' action 'iptables-multiport'

Delete that override and let the packaged nftables action stand, or, if you manage the firewall entirely through ufw and want bans to show up there, set banaction = ufw in [DEFAULT]. Restart and confirm the rule appears with sudo nft list ruleset | grep f2b.

Fail2ban will not start after editing jail.local. A typo — a stray heading or a bad time value — makes the service refuse to come up. Ask Fail2ban to check the config before it runs:

sudo fail2ban-client -t

It names the file and the jail with the problem, for example Errors in jail 'sshd'. Skipping..., so you fix the source rather than guessing.

FAQ

Does the stock Fail2ban install on Ubuntu 24.04 actually ban SSH attacks?

Yes. The package ships /etc/fail2ban/jail.d/defaults-debian.conf, which enables the sshd jail, sets backend = systemd so it reads the systemd journal instead of the missing /var/log/auth.log, and sets banaction = nftables so bans are enforced through Ubuntu's real firewall. A plain apt install fail2ban protects SSH from first boot. Confirm it with sudo fail2ban-client status sshd and look for a non-zero Total failed.

Why is Fail2ban not banning anything on my box?

Rule out the three common causes in order. You may be testing from an address in ignoreip, which is exempt by design. You may have overridden the working default by pasting backend = auto into jail.local from an old guide, which breaks journal reading on an image with no auth.log. Or you may be inside a container with no systemd journal to read at all. Check Total failed in fail2ban-client status sshd: if it never climbs while journalctl -u ssh shows real failures, the jail is reading the wrong place.

How do I unban my own IP address?

Run sudo fail2ban-client set sshd unbanip YOUR.IP.HERE, which returns 1 on success, or sudo fail2ban-client unban --all to clear every ban. If you are locked out of SSH, use your provider's web or VNC console to run the same command — the ban rejects every packet from your address to port 22, so even a session that was already open stops working. Then add your address to ignoreip so it cannot recur.

What is the difference between jail.conf and jail.local?

jail.conf holds Fail2ban's upstream defaults and is overwritten on every package upgrade, so any edit there is eventually lost. The Debian/Ubuntu package layers its own settings on top through jail.d/defaults-debian.conf. Your changes belong in jail.local, which is read last and wins over both, and which upgrades never touch. Leave jail.conf as read-only reference.

Does Fail2ban replace key-based SSH authentication?

No. Fail2ban rate-limits repeated failures from one address; it does nothing against a slow, distributed guess where each address stays under the threshold. Key-only authentication (PasswordAuthentication no) makes password guessing impossible outright, and Fail2ban then trims log noise and evicts scanners early. Run both, and ideally keep SSH off the public internet entirely.