1. Overview
Netfilter is a packet filtering system of the Linux kernel. The traditional interface for managing Netfilter is iptables or its successor nftables, but becoming skilled in these tools can be difficult and time-consuming.
The Uncomplicated Firewall (UFW) is a command-line firewall abstraction layer that automatically uses either iptables or nftables as a back-end firewall. UFW is a tool that minimizes the effort of setting up a firewall by starting with an optimal default configuration. In many cases, it’s only necessary to know the name of the applications to be authorized. It has graphical frontends, like GUFW.
In this tutorial, we’ll see how to use UFW from scratch. Our test case is its configuration on a Linux server via SSH.
2. Precautions
UFW is available for all Linux distributions. On Ubuntu, it’s pre-installed but disabled by default. Some VPS providers, however, supply UFW pre-configured and pre-activated. So, first, we need to look at the status of UFW.
Let’s check the UFW status with three different testing machines. Our goal is to figure out the possible pitfalls under different initial scenarios. In all the upcoming examples, we’re logged in via SSH.
2.1. When UFW Is Pre-Activated
Our first test is on a just-rented VPS with UFW pre-activated:
# 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 Anywhere
22/tcp (v6) ALLOW IN Anywhere (v6)
In this case, the provider pre-configured UFW to use SSH only (on port 22). Therefore, any other service we’ll install, such as Apache, won’t be reachable unless we first add the necessary rules to UFW.
2.2. When UFW Is Inactive
Let’s repeat the same test on another machine with a different default:
# ufw status verbose
Status: inactive
So, the provider hasn’t set up the firewall in this case. We need to be careful, however, because if we activate UFW as it is, the unfixable may happen:
# ufw enable
Command may disrupt existing ssh connections. Proceed with operation (y|n)? y
Firewall is active and enabled on system startup
# exit
Connection to 94.177.244.236 closed.
$ ssh [email protected]
ssh: connect to host 94.177.244.236 port 22: Connection timed out
The connection went into timeout because, by activating UFW without any rule, UFW drops all incoming connections. So, from now on, we can’t connect to our server via SSH. If we haven’t a way to revert what we’ve just done (such as a snapshot or a VNC connection), we’re out of luck.
2.3. When UFW Is Not Installed
The previous examples were about two VPSs with Ubuntu Server. Let’s try a VPS with Fedora to repeat the same test:
# ufw status verbose
-bash: ufw: command not found
This time UFW isn’t pre-installed. However, this doesn’t mean the provider hasn’t pre-configured and started another firewall. To give it a try, let’s check firewalld, the default firewall on RHEL-based systems. Its command-line client is firewall-cmd:
# firewall-cmd --list-all
FedoraServer (active)
[...]
services: cockpit dhcpv6-client ssh
[...]
So, firewalld is active. The first step is to disable it if we want to use UFW. In fact, having two applications that simultaneously manage iptables or nftables is a troublemaker.
As a general rule, before installing and configuring UFW, we should ensure another firewall isn’t already active.
3. A Real-World Example
Luckily, the essential use of UFW is straightforward. Let’s see how to enable, disable and configure it on a server running a website.
To make sure we start from scratch, let’s disable and reset UFW to a default state:
# ufw disable
Firewall stopped and disabled on system startup
# ufw reset
Resetting all rules to installed defaults. This may disrupt existing ssh
connections. Proceed with operation (y|n)? y
[...]
We’re ready to install a web server. First, however, let’s set up the UFW default policy and look at UFW’s application profiles.
3.1. UFW Default Policy
A port is an unsigned integer from 0 to 65535 that allows the routing of data to a specific service. A port can be open, closed, or stealth.
The desired behavior of a firewall is to allow incoming connections only on previously authorized (open) ports, blocking all other (stealth) ports. That’s why the UFW default policy is “deny incoming traffic” and “allow outgoing traffic.” As a result, our server can send requests to the outside world and receive responses. At the same time, the firewall will block all unsolicited incoming connections.
Let’s make sure these rules are indeed the default by making them explicit:
# ufw default deny incoming
[...]
# ufw default allow outgoing
[...]
This default policy means that no port should be open. More precisely, all ports should be stealth. So the next step is to specify the list of ports to be opened. For this purpose, we’ll use application profiles.
3.2. List Available Application Profiles
Applications that require open ports can include a UFW profile, which details which ports need to be opened. These profiles are in the /etc/ufw/applications.d directory. Let’s see which applications have installed a profile:
# ufw app list
Available applications:
OpenSSH
At the moment, we have only one application. Let’s repeat the test after installing a web server, such as Apache:
# apt install apache2
[...]
# ufw app list
Available applications:
Apache
Apache Full
Apache Secure
OpenSSH
Then, let’s inspect each profile to know which ports it opens:
# ufw app info "Apache"
[...]
80/tcp
# ufw app info "Apache Full"
[...]
80,443/tcp
# ufw app info "Apache Secure"
[...]
443/tcp
# ufw app info "OpenSSH"
[...]
22/tcp
Actually, knowing the ports associated with each application profile isn’t strictly necessary. However, it makes us more aware of what we’re doing.
3.3. Enable Application Profiles
We’re interested in the “Apache Full” and “OpenSSH” profiles. Let’s enable them:
# ufw allow "Apache Full"
[...]
# ufw allow "OpenSSH"
[...]
UFW is now ready-to-use for our test case. Let’s enable it and check its status:
# ufw enable
[...]
# ufw status verbose
[...]
To Action From
-- ------ ----
80,443/tcp (Apache Full) ALLOW IN Anywhere
22/tcp (OpenSSH) ALLOW IN Anywhere
80,443/tcp (Apache Full (v6)) ALLOW IN Anywhere (v6)
22/tcp (OpenSSH (v6)) ALLOW IN Anywhere (v6)
Each rule is doubled because one applies to IPv4 and the other to IPv6.
3.4. Operation Confirmation by Port Scanning
According to our UFW rules, all ports must be stealth except 22 (SSH), 80 (HTTP), and 443 (HTTPS). Let’s check whether this is actually the case, using nmap to scan the server ports from a computer outside the server’s network:
$ nmap 217.69.7.111
[...]
Not shown: 997 filtered ports
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
443/tcp closed https
[...]
In this way, nmap scanned only the first 1,000 ports and reported 997 filtered ports. In the nmap lexicon, filtered and stealth are synonyms. Thus, except for the three ports specified in the UFW rules, all the others are stealth, precisely as we wanted.
We then note that ports 22 and 80 are open, as expected. In contrast, port 443 is closed, but we shouldn’t be surprised. In fact, in our default Apache installation, we have a virtual host only on port 80. As a consequence, port 443 is unused. We can verify this with apache2ctl, the Apache HTTP server control interface:
# apache2ctl -S
[...]
VirtualHost configuration:
*:80 127.0.1.1 (/etc/apache2/sites-enabled/000-default.conf:1)
[...]
Ultimately, UFW works. In general, port scanning is a method of confirming whether our firewall is working as intended.
3.5. Check the Logs and Disable Them
Usually, logs are inside the /var/log/ directory, and UFW is no exception. To view UFW logs, we can use the same tools helpful to check logs of other programs. For example, to read in real time the latest log entries, let’s run the following command:
# tail -f /var/log/ufw.log
[...]
Aug 3 15:00:20 TEST kernel: [175446.845047] [UFW BLOCK] IN=enp1s0 OUT= MAC=56:00:04:17:28:a9:fe:00:04:17:28:a9:08:00 SRC=45.143.203.59 DST=217.69.7.111 LEN=40 TOS=0x00 PREC=0x00 TTL=245 ID=59465 PROTO=TCP SPT=53743 DPT=4444 WINDOW=1024 RES=0x00 SYN URGP=0
This log is extremely verbose, and its analysis is beyond the scope of our tutorial. By default, it’s in “low” mode, which means that it logs all packets blocked or allowed by the defined policies. The other ways “medium,” “high,” and “full” are even more verbose.
If we don’t have a valid reason to keep UFW’s logging active, we can disable it because it can take up dozens of gigabytes, filling up our server’s storage:
# ufw logging off
Logging disabled
A public IP can receive a considerable number of connection attempts per second. Avoiding logging them relieves our server of a potentially cumbersome task.
4. Fine-Tuning the Rules
Application profiles aren’t always available and, in any case, aren’t the only strategy for using UFW. Let’s look at other possibilities.
4.1. List All Rules and Delete a Rule
When we need to delete a rule, we can use the original rule or, more simply, its number. Therefore, let’s look at the rules in numerical order:
# ufw status numbered
Status: active
To Action From
-- ------ ----
[ 1] Apache Full ALLOW IN Anywhere
[ 2] OpenSSH ALLOW IN Anywhere
[ 3] Apache Full (v6) ALLOW IN Anywhere (v6)
[ 4] OpenSSH (v6) ALLOW IN Anywhere (v6)
In this case, we can, for example, eliminate rules 3 and 4 since we’re not using IPv6. But there isn’t a function to remove multiple rules at once, so we must remove one rule at a time. We should be careful to remove the rules from the highest number to the lowest, so the other rules won’t change after the deletion:
# ufw delete 4
[...]
# ufw delete 3
[...]
# ufw status verbose
[...]
To Action From
-- ------ ----
80,443/tcp (Apache Full) ALLOW IN Anywhere
22/tcp (OpenSSH) ALLOW IN Anywhere
The changes are immediately active.
4.2. Enable Ports or a Range of Ports Without an Application Profile
Suppose we’ve created a special-purpose server on a nonstandard port (55530/TCP), running concurrently with Apache. In this case, we don’t have an application profile. However, we can manually specify the rule:
# ufw allow 55530/tcp
Rule added
Rule added (v6)
# ufw status verbose
[...]
To Action From
-- ------ ----
80,443/tcp (Apache Full) ALLOW IN Anywhere
22/tcp (OpenSSH) ALLOW IN Anywhere
55530/tcp ALLOW IN Anywhere
55530/tcp (v6) ALLOW IN Anywhere (v6)
We can also specify a range of ports. Let’s take, for example, the case of a WebRTC live streaming server that requires ports 5000 to 65000 to be opened on UDP protocol:
# ufw allow 5000:65000/udp
Rule added
Rule added (v6)
root@TEST:~# ufw status verbose
[...]
To Action From
-- ------ ----
[...]
5000:65000/udp ALLOW IN Anywhere
[...]
5000:65000/udp (v6) ALLOW IN Anywhere (v6)
Also, if we don’t have an application profile but know the service name, we can specify that. In such a case, UFW will use the standard ports for that service. Let’s say we want to open ports for an FTP server (whose standard port is 21):
# ufw allow ftp
Rule added
Rule added (v6)
# ufw status verbose
[...]
To Action From
-- ------ ----
[...]
21/tcp ALLOW IN Anywhere
[...]
21/tcp (v6) ALLOW IN Anywhere (v6)
To see the list of all supported services, we can inspect the /etc/services file:
# cat /etc/services
# Network services, Internet style
[...]
tcpmux 1/tcp # TCP port service multiplexer
echo 7/tcp
[...]
It’s a comprehensive list based on the “Service Name and Transport Protocol Port Number Registry.”
4.3. Restrict Allowed IPs
In many situations, we want to restrict access to services to specific IPs. For example, let’s limit SSH access (port 22) only to IP 1.2.3.4:
# ufw allow from 1.2.3.4 proto tcp to any port 22
Rule added
Then let’s remove the old SSH rule, being careful to be connected with the just-authorized IP:
# ufw status numbered
[...]
[ 2] OpenSSH ALLOW IN Anywhere
[...]
[ 6] 22/tcp ALLOW IN 1.2.3.4
[...]
# ufw delete 2
[...]
We should stay alert because our next SSH connection attempt will timeout if we haven’t inserted the proper IP.
4.4. Limit the Number of Connections to a Given Port Over Time
UFW has a rate-limiting feature that denies connections from an IP address that has attempted to initiate six or more connections in the last 30 seconds. This helps prevent brute force attacks.
Let’s reopen SSH connections to all IPs, but this time, with rate-limiting:
# ufw limit ssh
Rule added
Rule added (v6)
Then let’s remove the old rule:
# ufw status numbered
[...]
[ 5] 22/tcp ALLOW IN 195.231.79.38
[ 6] 22/tcp LIMIT IN Anywhere
[...]
[10] 22/tcp (v6) LIMIT IN Anywhere (v6)
root@TEST:~# ufw delete 5
[...]
From this point on, if we connect at least six times in 30 seconds (regardless of whether the login succeeds), UFW will block us. In this case, the port won’t become stealth but closed. But, we don’t need to worry, as this ban will expire after 30 seconds.
5. Conclusion
In this article, we’ve tested examples of UFW usage in a server environment.
We started from scratch. That’s why we analyzed only the most common cases. If we have particular needs, we need to study the documentation.
Also, it’s not possible to do everything with simple commands. For example, if we wanted to block ping responses, we would have to edit a UFW configuration file manually.