
Every internet-facing Linux server faces automated brute-force attacks within minutes of going live. A fresh Ubuntu 26.04 server with password-based SSH enabled will see thousands of failed login attempts before you even finish your initial setup. If you want to install Fail2Ban on Ubuntu 26.04 and stop those attacks cold, this guide walks you through the full process: installation, jail configuration, nftables integration, incremental banning, and daily management commands, all tested on Fail2Ban v1.1.0 with nftables 1.1.6 in April 2026.
By the end, you will have a working Fail2Ban setup that automatically bans brute-force attackers at the firewall layer, escalates penalties for persistent offenders, and lets you manage bans with a single command. This Linux server tutorial covers beginner-friendly explanations alongside the kind of production-grade tuning that experienced sysadmins actually use.
What Is Fail2Ban and Why Does Your Server Need It?
Fail2Ban is a log-driven intrusion prevention tool written in Python. It watches your service logs, runs each log entry through a regex filter, and when a single IP address exceeds the failure threshold you set, it automatically updates your firewall to block that IP.
On Ubuntu 26.04, Fail2Ban reads SSH failure events from systemd journald (not flat files like in older releases) and pushes bans into nftables, the default kernel packet filter. The result is that banned IPs get dropped before any application logic ever sees them.
Fail2Ban is not a web application firewall and it cannot detect low-and-slow attacks designed to stay under the rate limit. What it does well, and nothing else does as cheaply, is eliminate the noisy 90% of drive-by SSH scanners and login bots with about 15 MB of RAM and negligible CPU usage.
How the Core Concepts Work
Before touching a config file, you need to understand three terms that appear throughout every Fail2Ban guide:
- Jail: A paired combination of a filter and an action. The SSH jail watches SSH logs and bans offenders.
- Filter: A set of regex patterns that match failure events in a specific log source.
- Action: What Fail2Ban does when the ban threshold is hit, typically inserting a firewall rule.
Fail2Ban Version Differences Across Ubuntu LTS
The firewall backend and log source differ across Ubuntu releases. Here is what ships by default on each:
| Ubuntu Release | Fail2Ban Version | Firewall Backend | SSHD Log Backend |
|---|---|---|---|
| 26.04 LTS | 1.1.x | nftables | systemd journald |
| 24.04 LTS | 1.0.x | nftables | systemd |
| 22.04 LTS | 0.11.x | iptables-multiport | /var/log/auth.log |
On Ubuntu 26.04, the packaged defaults-debian.conf file handles backend selection automatically. You do not need to manually set the firewall or log backend unless you are overriding distro defaults.
Prerequisites
Before you begin this Linux server tutorial, confirm the following:
- Operating System: Ubuntu 26.04 LTS (Resolute Raccoon) on a server or VPS
- User Privileges: A non-root user with sudo access
- Active SSH session: Keep your current session open throughout. Do not close it until you have verified your own IP is whitelisted.
- Firewall: UFW or nftables already active (recommended but not required)
- Terminal access: Comfortable running commands in a bash shell
If your account is not in the sudo group, add it with sudo usermod -aG sudo yourusername before continuing.
Step 1: Update Your System Before Installation
Always refresh your package index and bring existing packages current before installing new software. Running an install on an out-of-date system can trigger dependency conflicts that are frustrating to diagnose.
sudo apt update && sudo apt upgrade -y
Why this matters: The apt update command refreshes your local list of available packages from the Ubuntu repositories. Without it, APT might install an older cached version of Fail2Ban and miss recent security patches. The upgrade command applies pending updates so you start from a clean, consistent base.
If you are on a minimal Ubuntu 26.04 install and APT reports Unable to locate package fail2ban, the Universe component is not enabled. Enable it first:
sudo add-apt-repository universe
sudo apt update
Why Universe is required: Fail2Ban is maintained by the community, not Canonical’s core team, so it ships through the Universe component rather than the main archive. The add-apt-repository universe command enables that component for your system.
Step 2: Install Fail2Ban on Ubuntu 26.04
With the package index refreshed, install Fail2Ban with a single command:
sudo apt install fail2ban -y
Why no PPA is needed: Ubuntu 26.04 ships Fail2Ban 1.1.x directly in its official Universe repository. There is no need to add third-party PPAs or compile from source. APT handles all Python3 dependencies automatically.
After installation completes, verify the binary is present and check the version:
fail2ban-client --version
Expected output on Ubuntu 26.04:
Fail2Ban v1.1.0
Now enable the service so it starts automatically after every reboot and launch it immediately:
sudo systemctl enable fail2ban --now
Why the --now flag: The --now flag combines two steps into one. It registers Fail2Ban with systemd’s boot targets (enable) and starts the process right now (start) without requiring a reboot. Without it, Fail2Ban would only start protecting your server after the next restart.
Confirm the service is healthy:
sudo systemctl status fail2ban
Look for active (running) in the output. A healthy service looks like this:
fail2ban.service - Fail2Ban Service
Loaded: loaded (/usr/lib/systemd/system/fail2ban.service; enabled)
Active: active (running)
Main PID: 1234 (fail2ban-server)
Memory: 12.4M
If the service fails to start at this stage, run sudo journalctl -u fail2ban -n 20 --no-pager to read the error before continuing.
Step 3: Understand jail.conf vs jail.local
This step has nothing to do with security settings, but it is where most misconfigurations begin.
Fail2Ban ships two baseline configuration files:
/etc/fail2ban/jail.conf: The package-owned default. APT overwrites this file on every upgrade./etc/fail2ban/jail.d/defaults-debian.conf: Ubuntu-specific overrides for the firewall and log backend./etc/fail2ban/jail.local: Your custom file. APT never touches this file.
Why you must never edit jail.conf: When APT upgrades the Fail2Ban package, it replaces jail.conf with the new version. Any changes you made in that file disappear silently. The .local file system exists specifically to survive upgrades. Fail2Ban reads .local files after their .conf counterparts and merges only the keys you define.
Create the override file now:
sudo touch /etc/fail2ban/jail.local
Keep jail.local minimal. Only add the settings you actually want to change. A short, focused file is far easier to audit and troubleshoot than a 300-line copy of jail.conf.
Step 4: Configure Global Defaults in jail.local
Open the file in your editor:
sudo nano /etc/fail2ban/jail.local
Paste in the following [DEFAULT] block. This is the same configuration used on the Ubuntu 26.04 test VM:
[DEFAULT]
bantime = 1h
findtime = 10m
maxretry = 5
backend = systemd
banaction = nftables[type=multiport]
banaction_allports = nftables[type=allports]
ignoreip = 127.0.0.1/8 ::1 YOUR_ADMIN_IP
Replace YOUR_ADMIN_IP with your actual administration IP address. Save the file with Ctrl+O, then Enter, then Ctrl+X.
Here is what each directive does and why each value was chosen:
bantime = 1h— How long an offending IP stays blocked. One hour is intentionally short for first offenses; most drive-by scanners move on after one rejection. Persistent attackers get escalated bans through therecidivejail configured later.findtime = 10m— The sliding window Fail2Ban counts failures in. Any IP that hitsmaxretryfailures inside 10 minutes gets banned. This catches burst attacks without falsely flagging a user who typed their password wrong over several hours.maxretry = 5— Global failure threshold before a ban fires. Five attempts is forgiving for most services. The SSH jail overrides this to 3 below.backend = systemd— Explicitly tells Fail2Ban to read logs from systemd journald, not flat files. On Ubuntu 26.04, SSH no longer writes to/var/log/auth.logby default. If you leave this asauto, Fail2Ban may pick the wrong backend and silently miss every SSH failure. This is the single most important setting for Ubuntu 26.04 compatibility.banaction = nftables[type=multiport]— Uses the nftables ban action, which integrates natively with Ubuntu 26.04’s default firewall. Banned IPs land in a named nftables set, not a flat iptables chain.ignoreip— Whitelists IPs that should never be banned. Always include127.0.0.1/8and::1because local processes occasionally trigger filters. Add your office or VPN IP here. Self-banning a production server during a maintenance window is an avoidable incident.
Before reloading, always test the config syntax:
sudo fail2ban-client -t
Expected output:
OK: configuration test is successful
Why test before reload: The -t flag validates every file in /etc/fail2ban/ without touching the running daemon. One syntax error in jail.local silently prevents jails from loading. Always run this before restarting the service.
Now apply the settings:
sudo systemctl reload fail2ban
Step 5: Configure the SSH Jail
The SSH jail is already enabled on Ubuntu 26.04 through defaults-debian.conf, but its default settings are too permissive for a production server. Add the following [sshd] block to jail.local to tighten them:
sudo nano /etc/fail2ban/jail.local
Add this block below your [DEFAULT] section:
[sshd]
enabled = true
port = ssh
mode = normal
maxretry = 3
findtime = 10m
bantime = 1h
Why maxretry = 3 specifically for SSH: Three failures in ten minutes is almost always a bot, not a legitimate user who forgot their password. SSH brute force tools typically fire hundreds of attempts per minute. Lowering the threshold from the global default of 5 to 3 cuts off most attacks after their third probe.
Why mode = normal: Fail2Ban 1.1.x introduced an aggressive mode for SSH that catches more attack patterns. For most servers, normal mode is the better starting point since aggressive mode has a higher false-positive risk on shared environments.
Note on non-standard SSH ports: If you run SSH on a port other than 22, change port = ssh to port = 2222 (or whatever port you use). Fail2Ban needs the correct port to build the right nftables rule.
Ubuntu 26.04 vs 24.04 journal difference: On Ubuntu 26.04, the packaged jail uses _SYSTEMD_UNIT=ssh.service as the journal match. On Ubuntu 24.04, the match is _SYSTEMD_UNIT=sshd.service. The defaults-debian.conf file handles this automatically, but knowing the difference is useful when writing custom filters.
Restart and verify:
sudo systemctl restart fail2ban
sudo fail2ban-client status sshd
Expected output:
Status for the jail: sshd
|- Filter
| |- Currently failed: 0
| |- Total failed: 0
| `- Journal matches: _SYSTEMD_UNIT=ssh.service + _COMM=sshd
`- Actions
|- Currently banned: 0
|- Total banned: 0
`- Banned IP list:
The Journal matches line confirms Fail2Ban is reading from systemd journald, not a flat file. If you see File list: here instead, the backend is set incorrectly.
Step 6: Set Up Incremental Banning for Repeat Offenders
A one-hour ban barely slows a determined attacker who rotates through IPs. Incremental banning doubles the penalty for each subsequent offense, and the recidive jail applies a long ban to any IP that keeps returning.
Add these settings to the [DEFAULT] section in jail.local:
bantime.increment = true
bantime.factor = 2
bantime.formula = ban.Time * (1<<(ban.Count if ban.Count<20 else 20)) * banFactor
Why incremental bans work: Each time an IP hits the ban threshold again after its ban expires, the new ban duration doubles. The formula caps the multiplier at 20 iterations to prevent absurdly long ban times from accumulating forever. For most bots, the escalating cost makes your server not worth targeting.
Now add the recidive jail directly below your [sshd] block:
[recidive]
enabled = true
logpath = /var/log/fail2ban.log
bantime = 1w
findtime = 1d
maxretry = 3
Why the recidive jail matters: The recidive jail reads Fail2Ban’s own log file. Any IP that receives 3 bans across any jail within a 24-hour window gets a week-long ban on all ports. This single addition converts a simple brute-force defense into something that actually discourages persistent attackers from targeting your server.
After saving, test and reload:
sudo fail2ban-client -t
sudo fail2ban-client reload
Step 7: How Fail2Ban Uses nftables on Ubuntu 26.04
Confirm the nftables Rules
After Fail2Ban bans its first IP (you will trigger one in the next step), inspect what it inserted into the nftables ruleset:
sudo nft list ruleset | head -30
You should see a dedicated table like this:
table inet f2b-table {
set addr-set-sshd {
type ipv4_addr
elements = { 198.51.100.17, 203.0.113.88 }
}
chain f2b-chain {
type filter hook input priority filter - 1; policy accept;
tcp dport 22 ip saddr @addr-set-sshd reject with icmp port-unreachable
}
}
Why this design is better than iptables chains: The chain priority filter - 1 means the ban fires before UFW’s filter chain processes any traffic. A banned IP never reaches your application. Also, adding or removing an IP is a single atomic set operation, not a full ruleset reload, which means there is zero packet gap during ban updates.
UFW Users: No Conflicts
You do not need to disable UFW. On Ubuntu 26.04, UFW drives nftables under the hood. Fail2Ban’s nftables action writes into its own dedicated f2b-table, completely separate from UFW’s chains. Both coexist without conflicts.
If you prefer bans to appear as UFW rules, set banaction = ufw in your [DEFAULT] block and restart Fail2Ban. Bans will then show up in sudo ufw status numbered as deny rules.
Step 8: Test That Fail2Ban Actually Bans Attackers
A config that looks correct on paper may silently fail if the journal match is wrong. This test confirms the full detection chain works end to end.
Before you begin: Run this test from a secondary machine, a second terminal session, or a VPN connection. Do NOT test from your only active SSH session. If your IP is in ignoreip, the ban will not trigger, which is the correct behavior.
Trigger the Ban
From a secondary host, generate 4 failed SSH attempts:
for i in $(seq 1 4); do
ssh -o PreferredAuthentications=password \
-o PubkeyAuthentication=no \
[email protected] 2>/dev/null
echo "attempt $i done"
done
Verify the Ban Fired
Back on the server, check the SSH jail status:
sudo fail2ban-client status sshd
The test IP should appear in the Banned IP list. Then confirm the nftables rule exists:
sudo nft list ruleset | grep f2b
You should see the banned IP inside the addr-set-sshd set. From the attacker’s perspective, their next connection attempt will return:
ssh: connect to host your-server port 22: Connection refused
That Connection refused response comes from nftables dropping the packet before SSH even sees it. Compare this to Permission denied (publickey,password) from a server that is not banning yet.
Day-to-Day Ban Management
These are the commands you will use regularly after the initial configure Fail2Ban on Ubuntu 26.04 setup is complete:
# List all active jails
sudo fail2ban-client status
# Check SSH jail status and banned IPs
sudo fail2ban-client status sshd
# Manually ban an IP immediately
sudo fail2ban-client set sshd banip 203.0.113.50
# Unban a locked-out legitimate user
sudo fail2ban-client set sshd unbanip 203.0.113.50
# Unban an IP across all jails at once
sudo fail2ban-client unban 10.0.1.99
# Reload config without losing active bans
sudo fail2ban-client reload
# Check a jail's current runtime settings
sudo fail2ban-client get sshd bantime
sudo fail2ban-client get sshd maxretry
Why reload instead of restart: fail2ban-client reload applies your updated jail.local without clearing in-flight bans or resetting failure counters. systemctl restart fail2ban wipes everything. Use restart only when you are changing the backend or upgrading Fail2Ban itself.
Monitoring Fail2Ban Logs
Fail2Ban writes every ban, unban, jail start, and error to /var/log/fail2ban.log.
Watch live activity:
sudo tail -f /var/log/fail2ban.log
A healthy log stream looks like this:
2026-04-14 08:25:13 fail2ban.filter [sshd] Found 203.0.113.89
2026-04-14 08:25:45 fail2ban.filter [sshd] Found 203.0.113.89
2026-04-14 08:26:02 fail2ban.actions [sshd] Ban 203.0.113.89
Search for a specific IP:
sudo grep "203.0.113.89" /var/log/fail2ban.log
For servers with centralized log shipping, use the systemd journal source directly:
sudo journalctl -u fail2ban -n 20 --no-pager
Why journal-based monitoring matters for production: If you ship logs to a SIEM like Elastic or Loki, Fail2Ban events flow into your centralized log pipeline automatically when you use journald. Flat-file log shipping requires an additional agent configuration.
Troubleshooting Common Fail2Ban Errors
1. Fail2Ban Fails to Start After Editing jail.local
Symptom: systemctl status fail2ban shows failed.
Why it happens: A typo, missing bracket, or invalid directive in jail.local causes the daemon to abort on startup.
Fix:
sudo fail2ban-client -t
sudo journalctl -u fail2ban -n 20 --no-pager
The -t flag shows you the exact line with the error. Correct it, then restart.
2. Jail Shows 0 Bans During an Active Brute-Force Attack
Symptom: sudo fail2ban-client status sshd shows Currently failed: 0 while journalctl -u ssh is full of authentication failures.
Why it happens: The most common cause on Ubuntu 26.04 is backend = auto or backend = pyinotify in jail.local, which directs Fail2Ban to watch flat log files. Since Ubuntu 26.04 SSH logs go exclusively to journald, Fail2Ban sees nothing.
Fix: Add backend = systemd explicitly to your [DEFAULT] block, then reload.
3. WARNING: “No file(s) found for glob /var/log/apache2/access.log”
Why it happens: You enabled the apache-badbots or another web server jail without having Apache installed. The log file path does not exist.
Fix: Disable any jail that references a service not running on your server:
# In jail.local
[apache-badbots]
enabled = false
4. WARNING: Determined Family ‘inet6’ Is Empty
Why it happens: Fail2Ban tries to initialize an IPv6 ban set, but the server has no IPv6 address configured. This is a cosmetic warning, not an error.
Fix: If the warning is cluttering your logs, add this to [DEFAULT]:
allowipv6 = false
5. ERROR: Jail ‘sshd’ Already Banned This IP
Why it happens: You ran fail2ban-client set sshd banip on an IP already in the ban set. Fail2Ban refuses to insert duplicates.
Fix: Unban first, then re-ban if you need to extend the duration:
sudo fail2ban-client set sshd unbanip 203.0.113.50
sudo fail2ban-client set sshd banip 203.0.113.50
Production Hardening Tips
After you install Fail2Ban on Ubuntu 26.04 and verify it works, these additional steps raise the security baseline significantly:
- Raise
bantimeto 24h for SSH in production. A one-hour ban is appropriate for testing. On a live server with real attackers, a 24-hour ban combined withrecidivemakes your server not worth targeting for most botnets. - Disable password authentication entirely. Set
PasswordAuthentication noin/etc/ssh/sshd_config. With SSH keys as the only auth method, Fail2Ban becomes a secondary layer, not your primary defense. - Keep
ignoreipin version control. IP drift between maintenance windows causes self-lockouts. Storing the list in Git means one source of truth across multiple servers. - Add your WireGuard or OpenVPN CIDR to
ignoreip. If you connect through a VPN, your tunneled IP needs to be whitelisted or an expired VPN session can trigger SSH bans against your own traffic. - Servers behind Cloudflare require extra configuration. Configure Nginx to read the
CF-Connecting-IPheader and update your Fail2Ban filter to parse the real client IP. Without this change, you risk banning Cloudflare’s proxy ranges, which takes your site offline for all visitors.
Congratulations! You have successfully installed Fail2Ban. Thanks for using this tutorial for installing Fail2Ban on Ubuntu 26.04 LTS (Resolute Raccoon) system. For additional help or useful information, we recommend you check the official Fail2Ban website.