Fail2ban on Ubuntu VPS: Stop SSH Brute Force 2026
Hardening SSH with keys stops password attacks from succeeding, but the attempts keep coming — bots will hammer your server thousands of times a day regardless. Fail2ban turns that noise into action: it watches your log files, spots the pattern of repeated failures from a single IP, and tells the firewall to ban that address for a while. It is the automated bouncer for your VPS, and on Ubuntu 22.04 it takes about ten minutes to set up properly. This guide installs it, configures a tuned SSH jail, and verifies that bans actually fire.
jail.conf directly. Put all your settings in jail.local, which overrides the defaults and survives package upgrades. Enable the [sshd] jail, set a sane maxretry and bantime, and always confirm with fail2ban-client status sshd that the jail is loaded and watching the right log — a silent misconfiguration protects nothing.Install Fail2ban
Fail2ban is in the standard Ubuntu repositories, so installation is one command:
1sudo apt update
2sudo apt install fail2ban
The service starts and enables itself on install. Confirm it is running:
1sudo systemctl status fail2ban
You want to see active (running). Out of the box, modern Fail2ban already applies a basic SSH jail, but the defaults are conservative — the real value comes from tuning, which is the next step.
Create jail.local
The package ships /etc/fail2ban/jail.conf, but you must never edit it — an upgrade overwrites it and your settings vanish. Instead, create /etc/fail2ban/jail.local, which Fail2ban reads last and lets your values win:
1sudo nano /etc/fail2ban/jail.local
Add a [DEFAULT] block for global tuning followed by the SSH jail:
1[DEFAULT]
2# Ban for one hour
3bantime = 1h
4# A host is banned if it fails 5 times...
5maxretry = 5
6# ...within a 10-minute window
7findtime = 10m
8# Never ban your own admin IP
9ignoreip = 127.0.0.1/8 ::1 203.0.113.10
10
11[sshd]
12enabled = true
13port = ssh
Three settings do the work. maxretry is how many failures are tolerated, findtime is the window they must occur within, and bantime is how long the offender is locked out. With the values above, five failures inside ten minutes earns a one-hour ban.
The ignoreip line is your safety net: list your home or office IP there so a bad day of typos never bans you. Replace 203.0.113.10 with your real address.
Tune the Aggressiveness
The defaults are a reasonable start, but a public server facing constant attack benefits from a firmer hand. Two adjustments are common.
First, escalate repeat offenders. Fail2ban can lengthen the ban each time the same IP returns:
1[DEFAULT]
2bantime.increment = true
3bantime.factor = 2
4bantime.maxtime = 1w
With incrementing enabled, a host that comes back after its first one-hour ban gets two hours, then four, capped at one week. Persistent attackers effectively ban themselves out of existence.
Second, if your SSH runs on a custom port (say you moved it to 2222 during hardening), tell the jail:
1[sshd]
2enabled = true
3port = 2222
Without this, the jail watches the wrong port and never matches your real SSH traffic.
Apply and Confirm the Jail Loaded
Restart Fail2ban to load jail.local:
1sudo systemctl restart fail2ban
Now verify — this is the step people skip, and skipping it is how you end up with a firewall that protects nothing. Ask Fail2ban directly which jails are active:
1sudo fail2ban-client status
You should see sshd in the jail list. Drill into the SSH jail to confirm it is watching the correct log file and to see live counters:
1sudo fail2ban-client status sshd
The output reports the number of currently failed attempts, the total banned, and crucially the file list — it should point at the journal or /var/log/auth.log. If the file list is empty or wrong, the jail is loaded but blind, and you must fix the logpath or backend before it does anything useful.
Protect More Than Just SSH
SSH is the obvious target, but Fail2ban can watch any service that logs authentication failures. If you run a web server, two additional jails are worth enabling in jail.local.
The first guards against attackers probing for valid HTTP auth credentials behind Nginx:
1[nginx-http-auth]
2enabled = true
3port = http,https
4logpath = /var/log/nginx/error.log
The second is the recidive jail — a meta-jail that watches Fail2ban's own log and applies a much longer ban to any IP that keeps getting banned across jails:
1[recidive]
2enabled = true
3bantime = 1w
4findtime = 1d
5maxretry = 5
The recidive jail is the answer to attackers who wait out a short ban and immediately return. An IP banned five times in a day earns a full week in the cooler, regardless of which service it was attacking. Restart Fail2ban after adding jails, then confirm both appear in fail2ban-client status.
Only enable jails for services you actually run — a jail pointed at a log file that does not exist will fail to start, and systemctl status fail2ban will show the error.
Watch It Work and Manage Bans
On a public server you will not wait long for the first ban. Follow the Fail2ban log to see decisions in real time:
1sudo tail -f /var/log/fail2ban.log
Lines reading Ban 198.51.100.23 confirm the system is actively blocking attackers. To see who is currently banned, the status sshd output lists every banned IP.
If you ever need to release an address — for instance, you fat-fingered your own login — unban it manually:
1sudo fail2ban-client set sshd unbanip 203.0.113.10
Where Fail2ban Fits
Fail2ban is reactive: it punishes hosts after they misbehave, which complements the proactive layers around it. Key-only SSH ensures those banned attempts could never have succeeded anyway, the UFW firewall keeps unused ports closed entirely, and Fail2ban cleans up the persistent abusers probing what remains open.
Install it, drive everything through jail.local, tune maxretry and bantime to your tolerance, and — every time — run fail2ban-client status sshd to prove the jail is loaded and watching. A Fail2ban that is installed but misconfigured offers a false sense of security; one that is verified turns your log noise into automatic, escalating bans.