Get Rewarded! We will reward you with up to €50 credit on your account for every tutorial that you write and we publish!

Securing SSH on Ubuntu with UFW and Fail2ban

profile picture
Author
Egemen KEYDAL
Published
2026-06-17
Time to read
7 minutes reading time

Introduction

Put a server on the internet and the SSH login attempts start almost immediately. The vast majority are automated bots working through common username and password lists. Two tools handle this well on Ubuntu: UFW as a simple firewall, and Fail2ban to watch the auth logs and block whoever keeps failing to log in.

By default these two don't talk to each other. Fail2ban installs its bans as raw iptables rules, while your visible firewall is UFW. That works, but your bans then live outside ufw status, which is confusing when you later try to audit what is blocked. This tutorial sets both up and, more importantly, tells Fail2ban to apply its bans through UFW, so every ban shows up as a normal UFW rule.

Prerequisites

  • A server running Ubuntu 22.04 / 24.04 / 26.04 (this was tested on a fresh Ubuntu install).
  • Root access, or a user with sudo.
  • SSH access to the server. If you are new to UFW itself, the tutorial "Simple Firewall Management with UFW" covers the basics of rules.

Example terminology

This tutorial uses the following example values. Replace them with your own:

  • IPv4 address of the server: 10.0.0.1
  • Example attacker address used for the ban test: 198.51.100.23

Step 1 - Install Fail2ban

UFW already ships with Ubuntu, so there is nothing to install for the firewall itself. You can confirm it is present:

ufw version

On a fresh Ubuntu server this returns:

ufw 0.36.2
Copyright 2008-2023 Canonical Ltd.

Fail2ban is not installed by default. Update the package list and install it:

sudo apt update
sudo apt install fail2ban

One thing to know about Ubuntu 22.04: after the package is installed, the service is left disabled and stopped. You can check:

systemctl is-enabled fail2ban
systemctl is-active fail2ban

Output:

  • Disabled
    disabled
    inactive
    Leave it stopped for now. If you start it at this point it just loads the default jail with the default iptables ban action, which is exactly the behavior we want to change. We start it in Step 3, once the configuration is in place.

  • Enabled
    enabled
    active
    Stop it for now. We will restart it in Step 3, once the configuration is in place.
    sudo systemctl stop fail2ban

Step 2 - Set up the UFW firewall

Before enabling the firewall, set the default policy to block incoming traffic and allow outgoing traffic:

sudo ufw default deny incoming
sudo ufw default allow outgoing

Now the important part. The default policy blocks everything coming in, including your SSH session. If you enable UFW now, you will lock yourself out. Allow SSH first:

sudo ufw allow OpenSSH

OpenSSH is an application profile that UFW ships with; it opens port 22/tcp. You can see the available profiles with ufw app list.

With SSH allowed, enable the firewall:

sudo ufw --force enable

The --force flag skips the interactive "this may disrupt existing ssh connections" prompt, which is what you want when you are already running over SSH and have allowed it.

Check the result:

sudo ufw status verbose
Status: active
Logging: on (low)
Default: deny (incoming), allow (outgoing), disabled (routed)
New profiles: skip

To                         Action      From
--                         ------      ----
22/tcp (OpenSSH)           ALLOW IN    Anywhere
22/tcp (OpenSSH (v6))      ALLOW IN    Anywhere (v6)

The firewall is active, only SSH is open, and your session is still alive.

Step 3 - Configure Fail2ban to ban through UFW

Never edit /etc/fail2ban/jail.conf directly. It gets replaced on package updates. Fail2ban reads /etc/fail2ban/jail.local on top of it, so put your settings there.

Create /etc/fail2ban/jail.local:

sudo nano /etc/fail2ban/jail.local

Add the following:

[DEFAULT]
# Ban for 1 hour after 5 failures within 10 minutes
bantime  = 1h
findtime = 10m
maxretry = 5

# Enforce bans through UFW instead of raw iptables
banaction = ufw

[sshd]
enabled = true
backend  = systemd

Two lines do the real work here:

Description
banaction = ufw tells Fail2ban to use the ufw action that ships in /etc/fail2ban/action.d/ufw.conf, instead of its default iptables action. Bans become UFW rules.
backend = systemd For the sshd jail. On Ubuntu 22.04, OpenSSH logs to the systemd journal, so the journal backend is the reliable way for Fail2ban to read failed logins.

Save the file. Now enable and start the service in one step:

sudo systemctl enable --now fail2ban

Confirm the jail is running:

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:

Nothing is banned yet, which is expected. The Journal matches line confirms Fail2ban is reading SSH events from the journal.

Step 4 - Test that bans land in UFW

You don't want to wait for a real attacker to confirm the integration works. You can ban an address by hand and watch it appear in UFW. The address 198.51.100.23 below is a reserved documentation address, so this is safe to run.

Ban it:

sudo fail2ban-client set sshd banip 198.51.100.23

The jail now lists it:

sudo fail2ban-client status sshd
`- Actions
   |- Currently banned:	1
   |- Total banned:	1
   `- Banned IP list:	198.51.100.23

Now look at UFW:

sudo ufw status numbered
[ 1] Anywhere                   REJECT IN   198.51.100.23

This is the point of the whole setup. Fail2ban inserted a REJECT rule at position 1, above your allow rules, so the banned address is dropped before it ever reaches SSH. The ban is a normal UFW rule you can see and audit.

Remove the test ban:

sudo fail2ban-client set sshd unbanip 198.51.100.23

Check that the rule is gone:

sudo ufw status | grep 198.51.100.23

This returns nothing, which means Fail2ban removed the UFW rule when it unbanned the address.

From now on, any address that fails the SSH login five times within ten minutes gets the same treatment automatically, banned for one hour. If you watch ufw status numbered on a server that has been online for a while, you will see these rules come and go on their own.

Conclusion

You now have a UFW firewall that only allows SSH, and a Fail2ban jail that watches the SSH journal and blocks repeated failures by writing real UFW rules. Because the bans live in UFW, ufw status shows your full picture of what is blocked, instead of leaving Fail2ban's bans hidden in a separate iptables chain.

From here you can open additional ports as you add services (ufw allow 80/tcp, ufw allow 443/tcp), and add more Fail2ban jails for those services in the same jail.local file. Whenever you change jail.local, reload Fail2ban with:

sudo fail2ban-client reload
License: MIT
Want to contribute?

Get Rewarded: Get up to €50 in credit! Be a part of the community and contribute. Do it for the money. Do it for the bragging rights. And do it to teach others!

Report Issue

Discover our

Dedicated Servers

Get €20/$20 free credit!

Valid until: 31 December 2026 Valid for: 3 months and only for new customers
Configure now
Want to contribute?

Get Rewarded: Get up to €50 credit on your account for every tutorial you write and we publish!

Find out more