This guide explains how to install, check and tune fail2ban on a BinaryLane VPS running Ubuntu 24.04 LTS. It also covers whitelisting your IP address, managing bans and protecting nginx services.


Every server with a public IP address gets a steady stream of automated login attempts. fail2ban watches your logs, notices when one address fails too many times, and blocks that address at the firewall for a while.


This guide covers installing fail2ban, confirming what it is already doing, whitelisting your own IP so you cannot lock yourself out, tuning how aggressive it is, and managing bans day to day.


One thing worth knowing up front: on Ubuntu 24.04 the fail2ban package protects SSH as soon as it is installed. You do not have to enable anything to get the basic protection. Most of this article is about understanding and adjusting what you already have.




Before you start


Important: These changes are made inside your customer-managed operating system. Keep an existing SSH session open while changing access controls, and confirm you can open a second session before closing the first.


Prerequisites: 

You will need:

  • A Cloud Server running Ubuntu 24.04 LTS.
  • Root access over SSH, or a user with sudo.
  • Your own public IP address. You will need it for the whitelist step - there is a command below to find it.

Throughout this article, replace these example values with your own:

PlaceholderMeans
203.0.113.10Your server's public IP address
198.51.100.77An address that has been attacking your server
192.0.2.50Your own home or office IP address

 



TABLE OF CONTENTS



Step 1 - Install fail2ban


sudo apt update
sudo apt install -y fail2ban


During install you may see a block of Python SyntaxWarning messages mentioning fail2banregextestcase.py or servertestcase.py. These come from byte-compiling fail2ban's own test files and are harmless — the package still installs correctly.

The service starts and enables itself automatically. Confirm it is running:

sudo systemctl status fail2ban

 

● fail2ban.service - Fail2Ban Service
     Loaded: loaded (/usr/lib/systemd/system/fail2ban.service; enabled; preset: enabled)
     Active: active (running) since Tue 2026-09-22 13:17:25 AEST; 11s ago
   Main PID: 1843 (fail2ban-server)
     CGroup: /system.slice/fail2ban.service
             └─1843 /usr/bin/python3 /usr/bin/fail2ban-server -xf start

my-server fail2ban-server[1843]: Server ready

 

Note: The line WARNING 'allowipv6' not defined in 'Definition'. Using default one: 'auto'
appears in the log every time fail2ban starts. It is informational, not a
problem, and you can ignore it.

 



Step 2 - See what is already protected


sudo fail2ban-client status


Status
|- Number of jail:  1
`- Jail list:   sshd


A jail is one service fail2ban is watching, paired with the rules for how to respond. The sshd jail is already enabled because the Ubuntu package ships this file:

cat /etc/fail2ban/jail.d/defaults-debian.conf

 

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

[sshd]
enabled = true


Three things in that file matter, and two of them differ from what older fail2ban guides will tell you:

  • backend = systemd - fail2ban reads the systemd journal, not /var/log/auth.log. BinaryLane's Ubuntu 24.04 image includes rsyslog, so your server does have an auth.log - but fail2ban is not reading it, and changing logpath has no effect on the SSH jail.

  • banaction = nftables - bans are enforced with nftables, not iptables. Running iptables -L to look for bans will show you nothing. Use nft list ruleset instead.

  • [sshd] enabled = true - SSH is protected from the moment you install.

Look at the jail itself:


sudo fail2ban-client status sshd


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 out-of-the-box settings are:

SettingDefaultMeaning
maxretry5Failures allowed before a ban
findtime600 (10 minutes)Window those failures must happen within
bantime600 (10 minutes)How long the ban lasts


You can confirm them yourself:

sudo fail2ban-client get sshd maxretry
sudo fail2ban-client get sshd findtime
sudo fail2ban-client get sshd bantime

 



Step 3 - Whitelist your own IP address


Do this before you change anything else. fail2ban does not know the difference between an attacker and you mistyping a password three times. Out of the box, nothing is whitelisted:

sudo fail2ban-client get sshd ignoreip

 

No IP address/network is ignored


Addresses on the ignoreip list are never banned, no matter how many times they fail.

Find your own IP address:


Run this on your own computer, not on the server:

curl -4 https://ifconfig.me


On Windows PowerShell:

(Invoke-WebRequest -Uri https://ifconfig.me/ip).Content


If you are already logged in to the server over SSH, you can also ask the server which address you connected from:

echo $SSH_CLIENT | awk '{print $1}'


Add it to the whitelist permanently:


Create or edit /etc/fail2ban/jail.local and put your address on the ignoreip line, separated by spaces:

sudo nano /etc/fail2ban/jail.local

 

[DEFAULT]
ignoreip = 127.0.0.1/8 ::1 192.0.2.50


Keep 127.0.0.1/8 and ::1 - those stop the server banning itself. Add your own address after them. You can list several, and you can use CIDR notation for a whole range:

ignoreip = 127.0.0.1/8 ::1 192.0.2.50 203.0.113.0/24


Apply it:

sudo systemctl restart fail2ban


Check it took effect:

sudo fail2ban-client get sshd ignoreip

 

These IP addresses/networks are ignored:
|- 127.0.0.0/8
|- 192.0.2.50
`- ::1


Whitelisting works. Here is fail2ban's log after five deliberately failed logins from a whitelisted address - the failures are seen, and no ban is issued:

2026-09-22 13:23:23,533 fail2ban.filter [3085]: INFO [sshd] Ignore 192.0.2.50 by ip
2026-09-22 13:23:26,991 fail2ban.filter [3085]: INFO [sshd] Ignore 192.0.2.50 by ip
2026-09-22 13:23:29,198 fail2ban.filter [3085]: INFO [sshd] Ignore 192.0.2.50 by ip
2026-09-22 13:23:30,491 fail2ban.filter [3085]: INFO [sshd] Ignore 192.0.2.50 by ip
2026-09-22 13:23:32,490 fail2ban.filter [3085]: INFO [sshd] Ignore 192.0.2.50 by ip


Whitelist an address right now, without a restart:


If you are in a hurry - for example you are about to do something that will generate failures - you can add an address to the running server immediately:

sudo fail2ban-client set sshd addignoreip 192.0.2.50


To remove it again:

sudo fail2ban-client set sshd delignoreip 192.0.2.50


This is temporary. It applies instantly but is lost the next time fail2ban restarts, because it is never written to a config file. For anything permanent, edit jail.local as above.

If your IP address changes:


Most home and many office connections use a dynamic IP address that changes without warning. An ignoreip entry that was correct last month may now point at somebody else's address.

Because of this, do not treat ignoreip as your only safety net:

  • Keep your SSH key working. fail2ban bans on failed authentication - a working key does not generate failures, so key-based logins rarely trip it: Add an SSH key to an existing Ubuntu VPS

  • Remember the web console in mPanel. See How to use BinaryLane's mPanel. It connects to your server's virtual console rather than over the network, so a fail2ban rule cannot block it. This is the reliable way back in if you ever do lock yourself out.

  • If you have a static IP at the office, whitelist that rather than a changing home address.



Step 4 - Tune the ban settings


The defaults are reasonable but lenient. A tighter jail.local looks like this:

[DEFAULT]
# Addresses fail2ban will never ban. Put your own IP here first.
ignoreip = 127.0.0.1/8 ::1 192.0.2.50

# How long a ban lasts
bantime = 1h

# How far back to look for failures
findtime = 10m

# Failures within findtime before a ban
maxretry = 3

# Repeat offenders get progressively longer bans
bantime.increment = true
bantime.maxtime = 1w

[sshd]
enabled = true


Edit /etc/fail2ban/jail.local, never jail.conf. The jail.conf file is replaced when the fail2ban package is updated, and your changes would be lost. Anything in jail.local overrides it and survives upgrades.

Times accept suffixes: 600 (seconds), 10m, 1h, 1d, 1w.

Check the config before applying it:


sudo fail2ban-client -t


OK: configuration test is successful


If that passes, apply it:

sudo systemctl restart fail2ban


And confirm:

sudo fail2ban-client get sshd bantime
sudo fail2ban-client get sshd maxretry

 

3600
3


How escalating bans work:


With bantime.increment = true, an address that comes back after its ban expires gets a longer one. fail2ban works out the new ban like this:

bantime × 2 ^ (number of previous bans) × bantime.factor


bantime.factor defaults to 1, which gives a doubling sequence - 1×, 2×, 4×, 8× and so on - until it reaches bantime.maxtime. The first ban is always the base bantime; the increment only starts applying from the second.

Measured on a live server with bantime set to 60 seconds and the default factor:

first ban:    60 seconds
second ban:  120 seconds


Raising bantime.factor multiplies the whole sequence. With bantime.factor = 3 and the same 60 second base, the second ban is 360 seconds instead of 120.




Step 5 - Watch it work


After a burst of failed SSH logins from 198.51.100.77:

sudo fail2ban-client status sshd


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


The decision is recorded in fail2ban's own log:

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


2026-09-22 13:18:44,498 fail2ban.filter  [1843]: INFO   [sshd] Found 198.51.100.77 - 2026-09-22 13:18:44
2026-09-22 13:18:46,706 fail2ban.filter  [1843]: INFO   [sshd] Found 198.51.100.77 - 2026-09-22 13:18:46
2026-09-22 13:18:48,946 fail2ban.filter  [1843]: INFO   [sshd] Found 198.51.100.77 - 2026-09-22 13:18:48
2026-09-22 13:18:50,740 fail2ban.filter  [1843]: INFO   [sshd] Found 198.51.100.77 - 2026-09-22 13:18:50
2026-09-22 13:18:53,447 fail2ban.filter  [1843]: INFO   [sshd] Found 198.51.100.77 - 2026-09-22 13:18:53
2026-09-22 13:18:53,498 fail2ban.actions [1843]: NOTICE [sshd] Ban 198.51.100.77


Found is a failure being counted. Ban is the threshold being crossed.

The underlying SSH failures are in the journal:

sudo journalctl -u ssh --no-pager | grep -E 'Failed|Invalid'

 

Sep 22 13:18:48 my-server sshd[2071]: Invalid user baduser from 198.51.100.77 port 47396
Sep 22 13:18:50 my-server sshd[2071]: Failed password for invalid user baduser from 198.51.100.77 port 47396 ssh2
Sep 22 13:18:53 my-server sshd[2073]: Invalid user baduser from 198.51.100.77 port 42310
Sep 22 13:18:54 my-server sshd[2073]: Failed password for invalid user baduser from 198.51.100.77 port 42310 ssh2


And the actual firewall rule doing the blocking:

sudo nft list ruleset


table inet f2b-table {
    set addr-set-sshd {
        type ipv4_addr
        elements = { 198.51.100.77 }
    }

    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
    }
}


Because the rule rejects rather than drops, a banned client gets an immediate Connection refused rather than a connection that hangs:

ssh: connect to host 203.0.113.10 port 22: Connection refused


That is the clearest sign you are looking at a fail2ban ban rather than a network problem.

Seeing how long a ban has left:


sudo fail2ban-client get sshd banip --with-time

 

198.51.100.77   2026-09-22 13:19:58 + 3600 = 2026-09-22 14:19:58

 



Step 6 - Managing bans


Unban a single address:


sudo fail2ban-client set sshd unbanip 198.51.100.77


The firewall rule is removed straight away. The f2b-chain stays in place with an empty address set - that is normal, and it is ready for the next ban.

Be aware that unbanning also erases that address's ban history, so its next ban starts again at the base bantime rather than an escalated one.


Unban everything:


sudo fail2ban-client unban --all


Ban an address yourself:


sudo fail2ban-client set sshd banip 198.51.100.77


List every currently banned address across all jails:


sudo fail2ban-client banned


[{'sshd': ['198.51.100.77']}, {'nginx-http-auth': ['198.51.100.77']}, {'nginx-botsearch': []}]




Step 7 - Protecting other services


SSH is the default, but fail2ban ships filters for many services. List what is available:

ls /etc/fail2ban/filter.d/


Enable one by adding a section to jail.local. For nginx:

[nginx-http-auth]
enabled = true
backend = auto

[nginx-botsearch]
enabled = true
backend = auto
maxretry = 2


Why backend = auto matters here:


This is the most common reason a newly added jail never bans anything.


defaults-debian.conf sets backend = systemd for every jail. That is correct for SSH, which logs to the journal - but nginx writes its access and error logs to files in /var/log/nginx/, and its journal entries contain only service start and stop messages. A jail left on the systemd backend watches the journal, finds nothing to match, and sits silently at zero.


You can see the difference in the jail status. With the inherited backend:

|  `- Journal matches:  _SYSTEMD_UNIT=nginx.service + _COMM=nginx


After adding backend = auto:

|  `- File list:    /var/log/nginx/error.log


Set backend = auto on any jail whose service logs to a file rather than the journal.

Confirming it works:


Restart and check the jail list:


sudo systemctl restart fail2ban
sudo fail2ban-client status

 

Status
|- Number of jail:  3
`- Jail list:   nginx-botsearch, nginx-http-auth, sshd


Three failed HTTP basic-auth attempts produce this in the nginx error log:

2026/09/22 13:26:30 [error] 1946#1946: *1 user "siteadmin": password mismatch, client: 198.51.100.77, server: _, request: "GET /private/ HTTP/1.1", host: "203.0.113.10"


Which fail2ban picks up and bans on the web ports:

Status for the jail: nginx-http-auth
|  |- Total failed: 3
|  `- File list:    /var/log/nginx/error.log
`- Actions
   |- Currently banned: 1
   `- Banned IP list:   198.51.100.77

 

set addr-set-nginx-http-auth {
    type ipv4_addr
    elements = { 198.51.100.77 }
}
...
tcp dport { 80, 443 } ip saddr @addr-set-nginx-http-auth reject with icmp port-unreachable


Note the ports: this jail blocks 80 and 443 only, so a banned web client can still reach SSH. Each jail bans only the ports it is responsible for.

Testing a filter without waiting for an attack:


fail2ban-regex runs a filter against real log data and reports what it matched:


sudo fail2ban-regex systemd-journal /etc/fail2ban/filter.d/sshd.conf

 

Lines: 158 lines, 62 ignored, 24 matched, 72 missed


For a file-based service, pass the log path instead:

sudo fail2ban-regex /var/log/nginx/error.log /etc/fail2ban/filter.d/nginx-http-auth.conf

 

Lines: 4 lines, 0 ignored, 3 matched, 1 missed


If matched is 0, the filter is not recognising your log format and the jail will never ban anything.



Bans survive restarts and reboots


fail2ban keeps its bans in a SQLite database at /var/lib/fail2ban/fail2ban.sqlite3, so restarting the service or rebooting the server does not hand attackers a clean slate. After a reboot:

systemctl is-enabled fail2ban && systemctl is-active fail2ban
sudo fail2ban-client status sshd | tail -3

 

enabled
active
   |- Currently banned: 1
   |- Total banned: 1
   `- Banned IP list:   198.51.100.77


The log records the restored bans:

2026-09-22 13:24:03,953 fail2ban.actions [3345]: NOTICE [sshd] Restore Ban 198.51.100.77

 



fail2ban and ufw together


They coexist without any special configuration. fail2ban creates its own nftables table, inet f2b-table, with its chain at priority filter - 1:


table inet f2b-table {
    chain f2b-chain {
        type filter hook input priority filter - 1; policy accept;


ufw's rules sit at priority filter. The lower number runs first, so fail2ban bans are evaluated before ufw's rules - a banned address is rejected even if ufw allows the port.

Do not try to make fail2ban use ufw as its ban action. The default nftables action already works correctly alongside it.




Troubleshooting


fail2ban-client status says the service is not running. Check for a config error:

sudo fail2ban-client -t
sudo journalctl -u fail2ban -n 30 --no-pager


A typo in jail.local will stop the service starting.


A jail is enabled but never bans anything. Check backend first - see Step 7. Then run fail2ban-regex against the real log to confirm the filter matches.


Bans are not appearing in iptables -L. They will not. Ubuntu 24.04's fail2ban uses nftables. Use sudo nft list ruleset.


You cannot reach the server at all. If SSH gives Connection refused immediately, a ban is a likely cause. Use the web console in mPanel to get in - it bypasses the network entirely - then unban your address:


sudo fail2ban-client set sshd unbanip 192.0.2.50

Then add it to ignoreip in jail.local so it does not happen again.


You want to turn it off temporarily.


sudo systemctl stop fail2ban


Stopping the service removes its entire nftables table, so every ban is lifted immediately. You can confirm nothing of fail2ban's is left in the firewall:


sudo nft list tables | grep f2b-table

 

(no output — the table is gone)


Starting the service again restores every ban from the database:

sudo systemctl start fail2ban
sudo fail2ban-client status sshd | tail -1

 

   `- Banned IP list:   198.51.100.77


If you want the bans gone for good rather than just paused, clear them first - unban --all removes them from the database, so they do not come back:

sudo fail2ban-client unban --all
sudo systemctl stop fail2ban


To remove fail2ban entirely:


sudo apt remove --purge fail2ban

 



Things to know


  • SSH is protected the moment you install. Ubuntu's package enables the sshd jail for you. Everything after Step 1 is tuning, not activation.

  • Whitelist yourself before tightening anything. Lowering maxretry without an ignoreip entry is the most common way people lock themselves out.

  • ignoreip is not a guarantee. Dynamic IP addresses change. Keep SSH keys working and remember the mPanel console.

  • Edit jail.local, never jail.conf. Package updates overwrite jail.conf.

  • fail2ban is not a firewall. It reacts to failures in logs. It does not replace a properly configured firewall or SSH key authentication - it works alongside them.

  • It cannot stop a distributed attack. Banning by IP address does little against an attacker using thousands of them. Key-only SSH authentication is the stronger protection.

  • Bans are per-jail and per-port. A web ban does not block SSH, and vice versa.

  • backend = systemd is inherited by every jail. Set backend = auto on any jail watching a log file.





If you require assistance, feel free to submit a support ticket at our helpdesk here: Submit a ticket | BinaryLane