UFW – Uncomplicated Firewall
How many of us have Raspberry Pi’s running various home or enterprise projects? Do you run a version of Linux like Ubuntu, Mint and many others? Finally how many of you are running these with just the default operating system configuration?
Would it surprise you to learn default operating systems like Raspbian, Bullseye, Bookworm, Ubuntu and more aren’t hardened by default?
Yet millions of these devices are connected to the internet with little to no protection, because “Linux is Safe” … The mantra shared by many none cyber security professionals.
This article is aimed at the novice to help implement a basic firewall. I’ll cover the basics of Linux’s Uncomplicated Firewall (ufw) to configure some basic firewall starter rules.
The ufw command is a slightly more user friendly way of modifying internal IP Tables. You will require root to use this guide, or be in the sudo group. Or… You could just jump straight to the Basic Script and Cheat Sheet
Step 1 – Check ufw is installed
ufw … Let’s check it’s installed on your Linux distro.
sudo ufw status
ufw requires root privilege to run, even for the simplest of commands. It’s never a good idea to log in as root directly, it’s better to prefix commands with sudo (Super User Do), and get in the habit of consciously apply sudo when root privileges require it.
If you see the “Command not found” message, you will need to install ufw using :-
sudo apt install ufw
Once installed, ufw will be inactive or disabled, meaning this Firewall isn’t yet running on your device.
Step 2 – Start the ufw Service
You may be tempted to start the ufw service straight away, and that’s not a bad thing… except if you’re accessing your linux device remotely via SSH or VNC for example. For many projects my Linux machines are configured headless meaning I SSH or VNC into these devices for remote configuration, including those sat on my desk for testing and research purposes. I’m just too lazy to add an extra screen and keyboard to my lab set up.
Enabling ufw with the defaults, you’ll likely lock yourself out of your device, meaning you’ll need to go old school and attach a keyboard, mouse and display to log in directly and regain control.
Think about who can connect to your devices, are you permitting connections to other devices when connected to your local network? For example, if you’re running OctoPrint, homeAssistant or other opensource projects, do you only want devices connected to your router access? or do you need to allow remote connections outside your local network?
For now, I’m going to assume you’re running openSSH and VNC at a minimum on your linux device. Both of these services use Ports 22 and 5900 respectively.
For now, we’ll focus on local connections only. Your home router will typically have IP4 addresses starting with either 192.168.x.y IPv6 local Addresses are all prefixed with fe80:// If in doubt, log on to your router and see the addresses provided, or on your linux device type :-
ip address | grep inet
# or
ifconfig | grep inet
jason@TestWeb:~ $ ifconfig | grep inet
inet 192.168.2.246 netmask 255.255.255.0 broadcast 192.168.2.255
inet6 fe80::f243:1243:8967:765A prefixlen 64 scopeid 0x20<link>
inet 127.0.0.1 netmask 255.0.0.0
inet6 ::1 prefixlen 128 scopeid 0x10<host>
jason@TestWeb:~ $
In this instance, my IP Address is on the 192.168.x.y range (Class C)
Before we enable the firewall, we will at a minimum, need to allow access for SSH and VNC (If you’re using VNC). By default ufw denies incoming connections and allows outgoing connections, though I’ll explicitly declare them here.
jason@TestWeb:~ $ sudo ufw default deny incoming
Default incoming policy changed to 'deny'
(be sure to update your rules accordingly)
jason@TestWeb:~ $ sudo ufw default allow outgoing
Default outgoing policy changed to 'allow'
(be sure to update your rules accordingly)
jason@TestWeb:~ $
Next up we’ll allow connections to SSH (Port 22) and VNC (Port 5900). You should review the ports required for your project/environment and only enable the ports specific to your environment/set up.
There are two ways to allow incoming connections for service ports 22, 5900 either explicitly or by application name. To add a port specifically :-
jason@TestWeb:~ $ sudo ufw allow from 192.168.0.0/24 proto tcp to any port 22
Rules updated
jason@TestWeb:~ $ sudo ufw allow from fe80::/64 proto tcp to any port 22
Rules updated (v6)
Lets break this down. We want to allow connections from any address in the Class C range starting 192.168.0.0 through to 192.168.255.255 that uses the TCP Protocol to Port 22.
What’s this fe80::/64 ?
Great question! This covers IPv6 routing, you might not see this on your home router, however depending on model, your home router may assign IPv6 addresses. This nomenclature allows any local device to connect to the SSH port. I’ve added this as I’ve been locked out of my linux devices because my main machine was assigned an IPv6 address instead of an IPv4.
My head hurts… Is there an easier way? Well… for popular well known applications there is! You can add rules based on applications. Take a look and see which rulesets ufw is aware of (Note your output may be different depending on your version of ufw).
jason@TestWeb:~ $ sudo ufw app list
Available applications:
AIM
Bonjour
CIFS
CUPS
..
OpenSSH
POP3
POP3S
..
svnserve
jason@TestWeb:~ $ sudo ufw allow from 192.168.0.0/24 to any app openSSH
If you wanted to allow any connection from inside/outside your device network (includes the internet) to connect to the SSH Port you’d use the command :-
jason@TestWeb:~ $ sudo ufw allow openssh
Rules updated
Rules updated (v6)
I’m going to add VNC too as I’m using this in upcoming tutorials on hardening your environment.
jason@TestWeb:~ $ sudo ufw allow from 192.168.0.0/24 proto tcp to any port 5900
Rules updated
jason@TestWeb:~ $ sudo ufw allow from fe80::/64 proto tcp to any port 5900
Rules updated (v6)
jason@TestWeb:~ $
Was there an easier way? Of course, remember we can add rules based on applications so you could use :-
jason@TestWeb:~ $ sudo ufw allow vnc
Rules updated
Rules updated (v6)
This would allow connections to the VNC Ports from any network that can access your router, you really don’t want to do this…
Step 3 – Enable the Firewall
We’ve configured the basics… time to enable the firewall and check our handywork…
jason@TestWeb:~ $ sudo ufw enable
Command may disrupt existing ssh connections. Proceed with operation (y|n)? y
Firewall is active and enabled on system startup
jason@TestWeb:~ $ sudo ufw status verbose
Status: active
Logging: on (low)
Default: deny (incoming), allow (outgoing), disabled (routed)
New profiles: skip
To Action From
-- ------ ----
22/tcp ALLOW IN 192.168.0.0/24
22/tcp (OpenSSH) ALLOW IN 192.168.0.0/24
22/tcp (OpenSSH) ALLOW IN Anywhere
5900/tcp ALLOW IN 192.168.0.0/24
22/tcp ALLOW IN fe80::/64
22/tcp (OpenSSH (v6)) ALLOW IN Anywhere (v6)
5900/tcp ALLOW IN fe80::/64
If all has gone well, you’ll have a basic firewall configuration in place, now we have some clean up to perform!
Step 4 – Remove Errant Entries
Your firewall is enabled, giving a little more cyber security protection than you had at the start of the article, but wait! Did you spot the issue?
That’s right, we have an entry to allow SSH and VNC connections to our instance from potentially anywhere around the world! See the two entries highlighted below.
jason@TestWeb:~ $ sudo ufw status verbose
Status: active
Logging: on (low)
Default: deny (incoming), allow (outgoing), disabled (routed)
New profiles: skip
To Action From
-- ------ ----
22/tcp ALLOW IN 192.168.0.0/24
22/tcp (OpenSSH) ALLOW IN 192.168.0.0/24
22/tcp (OpenSSH) ALLOW IN Anywhere
5900/tcp ALLOW IN 192.168.0.0/24
22/tcp ALLOW IN fe80::/64
22/tcp (OpenSSH (v6)) ALLOW IN Anywhere (v6)
5900/tcp ALLOW IN fe80::/64
We definitely don’t want that (For the time being).
How do we delete them? There’s a couple of different ways…
First we can display the rules as a numbered list.
jason@TestWeb:~ $ sudo ufw status numbered
Status: active
To Action From
-- ------ ----
[ 1] 22/tcp ALLOW IN 192.168.0.0/24
[ 2] OpenSSH ALLOW IN 192.168.0.0/24
[ 3] OpenSSH ALLOW IN Anywhere
[ 4] 5900/tcp ALLOW IN 192.168.0.0/24
[ 5] 22/tcp ALLOW IN fe80::/64
[ 6] OpenSSH (v6) ALLOW IN Anywhere (v6)
[ 7] 5900/tcp ALLOW IN fe80::/64
jason@TestWeb:~ $
It’s a small list, so we can delete by rule number, so lets start with rule number 3.
jason@TestWeb:~ $ sudo ufw delete 3
Deleting:
allow OpenSSH
Proceed with operation (y|n)? y
Rule deleted
jason@TestWeb:~ $ sudo ufw status numbered
Status: active
To Action From
-- ------ ----
[ 1] 22/tcp ALLOW IN 192.168.0.0/24
[ 2] OpenSSH ALLOW IN 192.168.0.0/24
[ 3] 5900/tcp ALLOW IN 192.168.0.0/24
[ 4] 22/tcp ALLOW IN fe80::/64
[ 5] OpenSSH (v6) ALLOW IN Anywhere (v6)
[ 6] 5900/tcp ALLOW IN fe80::/64
The command was simple, we instruct ufw to delete rule 3, and deleting the next offending rule is just as straight forward… but! Prior to deleting Rule 3, the other rule to delete was Rule 6 yes? You have to be careful and always check the rule list again, since deleting a rule shifts the remaining rules up the list. If you were to delete 6 now, you’d delete a valid rule.
If you work in reverse order, delete 6 and then 3 then that’s valid.
jason@TestWeb:~ $ sudo ufw delete 5
Deleting:
allow OpenSSH
Proceed with operation (y|n)? y
Rule deleted
jason@TestWeb:~ $ sudo ufw status numbered
Status: active
To Action From
-- ------ ----
[ 1] 22/tcp ALLOW IN 192.168.0.0/24
[ 2] OpenSSH ALLOW IN 192.168.0.0/24
[ 3] 5900/tcp ALLOW IN 192.168.0.0/24
[ 4] 22/tcp ALLOW IN fe80::/64
[ 5] 5900/tcp ALLOW IN fe80::/64
But… There’s another way too!
Remember earlier you can add rules by application? You can delete them also.
jason@TestWeb:~ $ sudo ufw allow openssh
Rule added
Rule added (v6)
jason@TestWeb:~ $ sudo ufw delete allow openssh
Rule deleted
Rule deleted (v6)
Step 5 – Logs
Congratulations you’ve learned the basics of installing, applying default rules and firewall rules for your environment. There will be times when something may go wrong, or an application is unable to connect to your service. For this, take a look at the log files for clues. But… Where are they?
Logs are located in /var/log/ufw.log and by default logging level is set to LOW, you can change the logging level by using the following :-
sudo ufw logging off
sudo ufw logging low
sudo ufw logging medium
sudo ufw logging high
sudo ufw logging full
tail -10lf /var/log/ufw.log
Jan 16 17:37:41 Flightaware kernel: [3987529.320396] [UFW BLOCK] IN=wlan0 OUT= MAC=cd:ef:32:31:4e:20:be:5c:42:11:15:16:ff:13 SRC=192.168.56.142 DST=224.0.0.251 LEN=32 TOS=0x00 PREC=0x00 TTL=1 ID=28046 PROTO=2
Jan 16 17:38:04 Flightaware kernel: [3987552.143585] [UFW BLOCK] IN=wlan0 OUT= MAC=cd:ef:32:31:4e:20:be:5c:42:11:15:16:ff:13 SRC=192.168.56.142 DST=224.0.0.251 LEN=32 TOS=0x00 PREC=0x00 TTL=1 ID=25888 PROTO=2
Jan 16 17:38:48 Flightaware kernel: [3987596.425088] [UFW BLOCK] IN=wlan0 OUT= MAC=cd:ef:32:31:4e:20:be:5c:42:11:15:16:ff:12 SRC=192.168.56.1 DST=224.0.0.1 LEN=36 TOS=0x00 PREC=0x00 TTL=1 ID=0 DF PROTO=2
Useful for debugging firewall rules, and equally checking to see if anyone outside your system is trying to connect!
Step 5 – Logs with JournalCtl Enabled
Wait? what? you couldn’t find a file called /var/log/ufw.log?
Yep, as I was writing this tutorial and testing with the latest Raspberry Pi Debian build Bookworm. The rsyslog service isn’t enabled or even installed by default, it’s 2024, we’re encouraged to migrate to JournalCtl.
If you really want to go old school and have separate log files, you could use the following :-
sudo apt install rsyslog
sudo systemctl enable rsyslog.service
Not that it’s bad, but we should be moving forward with Journal Control… JournalCtl is quite a powerful tool, offering various command line filtering (Especially useful if your grep, sed and awk skills are rusty). If you want to know more a useful guide can be found :-
https://www.digitalocean.com/community/tutorials/how-to-use-journalctl-to-view-and-manipulate-systemd-logs
It’s worth noting that UFW Messages are logged with Kernel messages. We want to filter specifically on UFW Messages, use the following command, CTRL+C to exit.
jason@TestWeb:~ $ journalctl -f -k | grep -i UFW
The flags, -k means Kernel Log Entries and -f to follow (continue until your hit break (CTRL+C).
You could filter on just [UFW BLOCK] messages to see entries that are failing to connect to your device (Fault finding), or if another device is attempting to connect.
jason@TestWeb:~ $ journalctl -f -k | grep -i "[UFW BLOCK]"
If you want to see it in action, the nc or netcat command if your friend! If you’re on a Windows primary device for configuring your linux devices, you can download this tool from https://nmap.org/ncat/.
Since we’ve configured access to ports 22 and 5900 in this example, lets try and connect using a different port.
netcat 192.168.0.246 4321
In its basic form, were trying to establish a connection to your linux device on port 4321, replace the IP address with that of the target you’re trying to connect to, the command will eventually time out having not received a response. Check the ufw log and you’ll see entries similar to below.
Jan 16 18:54:22 TestWeb kernel: [UFW BLOCK] IN=eth0 OUT= MAC=cd:ef:32:31:4e:20:be:5c:42:11:15:16:ff:13 SRC=192.168.0.247 DST=192.168.0.246 LEN=64 TOS=0x00 PREC=0x00 TTL=64 ID=0 DF PROTO=TCP SPT=64033 DPT=4321 WINDOW=65535 RES=0x00 SYN URGP=0
Jan 16 18:54:31 TestWeb kernel: [UFW BLOCK] IN=eth0 OUT= MAC=cd:ef:32:31:4e:20:be:5c:42:11:15:16:ff:13 SRC=192.168.0.247 DST=192.168.0.246 LEN=48 TOS=0x00 PREC=0x00 TTL=64 ID=0 DF PROTO=TCP SPT=64033 DPT=4321 WINDOW=65535 RES=0x00 SYN URGP=0
Jan 16 18:54:38 TestWeb kernel: [UFW BLOCK] IN=eth0 OUT= MAC=cd:ef:32:31:4e:20:be:5c:42:11:15:16:ff:13 SRC=192.168.0.247 DST=192.168.0.246 LEN=48 TOS=0x00 PREC=0x00 TTL=64 ID=0 DF PROTO=TCP SPT=64033 DPT=4321 WINDOW=65535 RES=0x00 SYN URGP=0
Here we see the BLOCK message, the Interface the message was received on (eth0), the
MAC comprised of the Target Device MAC Address and the Source Device MAC Address (Physical),
SRC = Source Machine IP Address,
DST=Target Device IP Address,
SPT = Source Port,
DPT = Destination Port on Target Device (In this case 4321 was the port we tried to connect on).
WINDOW is the TCP Window Size,
RES shows the reserve bits set,
SYN = The TCP State Handshake that was attempted, received a SYN Packet
URGP = 0 means the connection was not established.
Clearly the connection attempt was denied, if you’re running wireshark either on the source of target device, it’s worth looking at the handshake that was attempted and failed to understand more about these rules.
Of course you can use netcat to connect to an open port, lets say Port 22 (SSH)
jason@Jasons-iMac ~ % nc 192.168.0.246 22
SSH-2.0-OpenSSH_9.2p1 Debian-2+deb12u2
In response we see a banner message response identifying the version of software being used for OpenSSH, we’ll cover this topic off in a later post on penetration testing, though it’s worth experimenting to see what else you can see when connecting to other well known ports!
If you want to know more about information stored in ufw logs, take a look here: https://linuxhandbook.com/ufw-logs/
Wrapping Up
Hopefully this will give you the basics to start configuring your firewall within your linux devices. There’s always more to learn, and google is your friend. ufw is a great start to hardening your device, though eventually you may end up with more complicated rules that need to be configured, limit connections and more. Although mostly achievable in ufw, rules will become complicated and challenging to read, you may want to look at other solutions.
Basic Script
This is a minimal script I tend to apply to my fresh instances when building new Linux releases, whether it’s Raspberry Pi or Ubuntu etc.
#!/bin/bash
sudo apt update -y && sudo apt upgrade -y
sudo apt install ufw
sudo ufw allow from 192.168.0.0/24 to any app openSSH
sudo ufw allow from fe80::/64 to any app openSSH
sudo ufw allow from 192.168.0.0/24 to any app VNC
sudo ufw allow from fe80::/64 to any app VNC
sudo ufw enable
Cheat Sheet
You can guarantee I always forget the correct syntax since it’s not often I configure the firewall or update rules these days.
sudo ufw enable
sudo ufw disable
sudo ufw reload
sudo ufw status
sudo ufw status verbose
sudo ufw status numbered
sudo ufw logging off
sudo ufw logging low
sudo ufw logging medium
sudo ufw logging high
sudo ufw logging full
sudo ufw allow log 22/tcp
sudo ufw allow log "openSSH"
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw reload
sudo ufw allow from 192.168.0.0/24 to any openSSH
sudo ufw allow from 192.168.0.0/24 to any port 22
sudo ufw allow from 192.168.0.0/24 to any port 22:25 proto tcp
sudo ufw allow proto tcp from 192.168.0.0/24 to any port 80,443
sudo ufw allow "openSSH"
sudo ufw deny "openSSH"
sudo ufw deny from 123.231.132.213
sudo ufw deny in on eth0 from 123.231.132.213
sudo ufw delete 1
sudo ufw delete allow from 123.231.132.213
sudo ufw delete allow "openSSH"
sudo ufw app list
journctl -k -f | grep -i "UFW"
journalctl --since yesterday -f -k | grep -i "UFW"
journalctl --since today -f -k | grep -i "UFW"
journalctl --since="2024-01-16 18:23:15" | grep -i "UFW"
journalctl --since="1 hour ago" | grep -i "UFW"
journalctl --since "2024-01-16 18:15:00" --until "2024-01-16 23:20:00" | grep -i "UFW"
tail -10lf /var/log/ufw.log
Finally
Thanks for stopping by, and hopefully this was useful, but why not drop a message in the comments, if there’s tricks and tips you can share with others, or spotted errors/clarifications needed.
Remember, your IoT Devices are only as secure as their weakest component!
Jason.


