Setting Up a Firewall using UFW: An In-Depth Guide
Guide to securing your server with UFW, covering setup, advanced rules, and best practices for effective firewall management.
Setting up a firewall is essential for securing your server, and UFW makes this process straightforward and user-friendly.
Think of it as your server's bouncer – it filters network traffic and only allows authorized access, keeping unwanted traffic out.
In this guide, I’ll walk you through setting up a firewall with UFW, as well as introduce you to advanced firewall rules that allow you to take your server's security to the next level.
I assume you're working on a properly set-up Ubuntu server. If not, check out my guide on preparing Ubuntu servers to get started.
Author's Note
Before we dive into the details, I’d like to highlight a few points.
Most guides online only cover the basics of UFW and how to add simple user-defined rules from the command line. It's hard to find a guide that goes beyond that.
This guide will take you beyond the basics and provide an example of how to implement advanced firewall rules. I'll explain some of the configuration and rule files, and where certain settings come from.
Understanding how UFW works, how to manage rules, and the order in which they take effect is essential, and I’ll do my best to make that clear in this guide.
If there’s anything you'd like me to cover, feel free to contact me, and I’ll either update this guide or publish a new one with the information you need, or help you if you encounter any issues.
I’m also in the process of writing a dedicated guide on Linux firewalls, focusing on the inner workings of UFW and how to develop advanced solutions with it.
Be sure to sign up for the newsletter to get notified once I publish it.
Installing UFW
On Debian-based distributions, like Ubuntu, UFW often comes pre-packaged.
You can check and install it using this command:
sudo apt install ufw
Many server providers configure the UFW firewall upon deploying the server to allow only SSH connections, enabling you to connect to the server.
If you have a server from Vultr, UFW is likely to be enabled by default. In my case with Hetzner, UFW is not enabled.
You can check the status of UFW and your current ruleset using this command:
sudo ufw status
The command’s output will either indicate that UFW is inactive, or that it is active with your current rule set.
If UFW is currently inactive, that’s fine, as we’ll proceed to configure it properly and enable it.
However, if UFW is already active, disable and reset it using the following commands:
sudo ufw disable
sudo ufw reset
You can re-enable it once you have added all the rules and finished configuring it.
UFW’s Default Policy
By default, UFW takes a secure approach by blocking all incoming traffic while allowing outgoing traffic from our server. This means our server can communicate externally, but it remains inaccessible to others.
Since there is no issue with our server reaching the outside world, there is no need to make any changes to that aspect.
However, to enable incoming traffic, it’s essential to selectively open only the required ports and authorize traffic through them.
You can find the default policy defined in the /etc/default/ufw
file:
DEFAULT_INPUT_POLICY="DROP"
DEFAULT_OUTPUT_POLICY="ACCEPT"
As you can see, the default policy for incoming traffic is set to DROP
, while the default policy for outgoing traffic is set to ACCEPT
.
If UFW is enabled, you can also review the default policy using the following command:
sudo ufw status verbose
You can modify this default behavior of UFW either by directly editing the file or by using these two commands:
sudo ufw default <policy> incoming
sudo ufw default <policy> outgoing
Replace <policy>
with either deny
, allow
or reject
.
deny
corresponds to DROP, allow
corresponds to ACCEPT, and reject
corresponds to REJECT.
Both DROP and REJECT policies prevent traffic from passing through the firewall, but they differ in their response messages.
With DROP, the traffic is silently discarded without any acknowledgment sent to the source. It neither forwards the packet nor responds to it.
On the other hand, REJECT sends an error message back to the source, signaling a connection failure.
UFW's Configuration Files
There are three files I’d like you to review.
While you may not need to modify them when configuring UFW and adding your rules, it's helpful to be familiar with them.
You may have already opened the /etc/default/ufw
file and examined the default policy of UFW, but there are other settings you might need to know about.
For example, you can disable IPv6 completely by changing the IPV6
variable from yes
to no
.
Additionally, you have the option to modify the default forward policy and the default application policy.
You can also enable UFW to manage the built-in chains by setting the MANAGE_BUILTINS
variable to yes
. Built-in chains are the default chains provided by Iptables, such as INPUT
, OUTPUT
, and FORWARD
.
UFW creates its own chains to manage its rules, such as ufw-user-input
and ufw-user-output
. These UFW chains are linked to the built-in chains to apply UFW’s firewall rules.
When MANAGE_BUILTINS
is set to yes
, on stopping or reloading UFW, it will flush the built-in chains completely, removing all rules (both UFW-managed and non-UFW rules) from INPUT
, OUTPUT
, and FORWARD
.
Any non-UFW rules in the built-in chains will be lost. UFW will take full control of the firewall, which may conflict with other tools that manage these chains.
For this reason, I don’t recommend enabling it unless you are fully aware of the implications.
The only change I make in this file is to the IPT_SYSCTL
variable. There’s another file I’ll discuss next, which is /etc/ufw/sysctl.conf
. UFW uses this file to tweak certain kernel parameters.
However, the original file for changing kernel parameters is /etc/sysctl.conf
.
While UFW uses its own version for this purpose, I prefer not to do that. When I harden the Linux kernel, I make my changes to kernel parameters directly in the /etc/sysctl.conf
file.
If UFW modifies the same parameters in its own file, my changes won’t take effect because UFW will override any corresponding parameters in the original sysctl.conf
file.
That’s why I configure UFW to use the original sysctl.conf
file like this:
IPT_SYSCTL=/etc/sysctl.conf
This way, I avoid dealing with two files and can maintain a clearer overview of the changes in a single file.
If you open the /etc/ufw/sysctl.conf
file, you’ll find that UFW has tweaked several kernel parameters. Once UFW is enabled, the new values for these parameters will take effect.
By default, UFW disables the acceptance of ICMP redirects, which helps prevent man-in-the-middle attacks. It also ignores bogus (invalid) ICMP errors and disables the logging of Martian packets.
Since I prefer not to manage kernel parameters in two places, I configure UFW to use the /etc/sysctl.conf
file instead.
To incorporate the default changes that UFW makes, I can simply add these to the end of the /etc/sysctl.conf
file:
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.default.accept_redirects = 0
net.ipv6.conf.all.accept_redirects = 0
net.ipv6.conf.default.accept_redirects = 0
net.ipv4.icmp_echo_ignore_broadcasts = 1
net.ipv4.icmp_ignore_bogus_error_responses = 1
net.ipv4.icmp_echo_ignore_all = 0
net.ipv4.conf.all.log_martians = 0
net.ipv4.conf.default.log_martians = 0
Now, simply reboot the server for the changes to take effect. There’s no need to enable UFW for the changes to apply, since we are using the original sysctl.conf
file.
The last file I want you to review is the /etc/ufw/ufw.conf
file, which contains just two variables:
ENABLED=no
LOGLEVEL=low
The first variable controls whether UFW is enabled or disabled. There's no need to change it manually, as enabling or disabling UFW from the command line will automatically update this value.
The second variable controls the log level of UFW. It can be set to off
, low
, medium
, high
, or full
, depending on how much logging detail you want.
You can also change the logging level directly from the command line using the following command:
sudo ufw logging <logging_level>
This will automatically update the value of the LOGLEVEL
variable.
UFW's Rule Files
In the /etc/ufw/
directory, you'll find files with the .rules
extension:
after6.rules after.rules before6.rules before.rules user6.rules user.rules
These files control how UFW manages incoming, outgoing, and forwarded traffic. Files with the number 6
handle IPv6 traffic, while files without it handle IPv4 traffic.
It's important to note that you should not modify the user.rules
or user6.rules
files directly, as any changes could be overwritten by UFW. Rules added by the user from the command line are saved to these files, which is why they are called user.rules
files.
You are free to add custom rules only to the before.rules
or after.rules
files.
The order in which UFW processes firewall rules is as follows: before.rules
first, user.rules
next, and after.rules
last.
For instance, if you place a rule to block HTTP traffic in before.rules
, it will be processed before any rule in the user.rules
file that might allow HTTP traffic.
The rules in the before.rules
files take priority, meaning they are executed first, allowing you to enforce more critical security measures, such as rules to block SYN flood attacks.
There are rare cases where I've had to add custom rules to the after.rules
file. While I don't think you'll need to do this, it's good to at least know it exists – just in case!
And it’s important to mention that all of UFW's rule files primarily use the Iptables syntax.
Understanding the order in which UFW processes these files and their respective roles is essential for effectively managing your firewall.
Basic User-Defined Rules
In the following, I'll cover the basic firewall rules that can be added from the command line.
UFW offers a set of commands for managing firewall rules directly, allowing you to quickly specify which services or ports are allowed or denied.
While these rules are designed for basic network access control and aren't intended for advanced use cases, they are perfect for setting up a firewall swiftly and efficiently.
sudo ufw show added
, as sudo ufw status
won’t display the rules in that case.Remember, every rule you add from the command line is saved to the user.rules
file, so feel free to check it as you add rules to understand how UFW translates the commands into the file.
Allowing and Denying Traffic
The core functionality of UFW is to allow or deny network traffic.
To allow or deny traffic for specific ports, you use the allow
or deny
rules, respectively.
To allow incoming traffic on port 22 (SSH):
sudo ufw allow 22
You can also specify a protocol (TCP or UDP):
sudo ufw allow 22/tcp
To deny traffic on port 80 (HTTP):
sudo ufw deny 80
If a range of ports is required, such as 5000-6000, use the following:
sudo ufw deny 5000:6000/tcp
Similarly, to deny traffic for the same range:
sudo ufw deny 5000:6000/tcp
You can also allow or deny traffic based on a service's name instead of specifying a port number. For example, to allow SSH traffic by using the service name, you can use:
sudo ufw allow ssh
UFW will then automatically determine the correct port (port 22 for SSH) and the associated protocol (TCP) to apply the rule. This approach simplifies rule management, especially when dealing with well-known services.
Application Profiles
Applications (software or services installed) can register their profiles with UFW upon installation, enabling UFW to manage them by name.
To view the available profiles, you can use the following command:
sudo ufw app list
If your server is new, you are more likely to see only the OpenSSH profile, which is the service behind SSH.
When using application profiles, there’s no need to memorize specific ports. Instead, you use the profile name.
For instance, to allow traffic on port 443 (HTTPS), you can use the following commands:
sudo ufw allow "NGINX HTTPS"
sudo ufw allow "Apache Secure"
If you’re curious about the origins of these profiles, check the /etc/ufw/applications.d/
directory.
The limit
Rule
The limit
rule in UFW helps protect against brute-force attacks by restricting the number of connection attempts an IP can make in a short time.
For example, when securing SSH, this rule lets legitimate users connect but temporarily blocks any IP that makes too many failed attempts.
By default, the rule allows only 6 connections from the same IP within 30 seconds. If the limit is exceeded, the IP is blocked temporarily, which reduces the risk of brute-force attacks.
To apply this rule for SSH traffic:
sudo ufw limit 22
This command enables SSH access while protecting the server from excessive connection attempts.
Access Control by IP or Subnet
UFW lets you control access based on IP addresses or subnets, which is useful for limiting access to specific clients or denying access from specific sources.
To allow all traffic from a specific IP to any port:
sudo ufw allow from 192.168.1.100
To allow SSH traffic only from a specific IP:
sudo ufw allow from 192.168.1.100 to any port 22
To allow traffic from a subnet:
sudo ufw allow from 192.168.1.0/24
To deny traffic from a specific IP address:
sudo ufw deny from 203.0.113.50
In some cases, you might want to specify not just the IP or subnet but also the protocol (TCP or UDP).
For example, to allow only TCP traffic from an IP address to port 80, you can specify the protocol as follows:
sudo ufw allow from 192.168.1.100 to any port 80 proto tcp
By specifying protocols in your access control rules, you gain more control over the traffic flow, ensuring that only the desired traffic (TCP or UDP) is allowed from specific sources or networks.
Enabling and Checking Status
Before activating our firewall, it’s crucial to review the rules we’ve added so far to prevent any unexpected behavior.
As I mentioned earlier, if the firewall is disabled, we can't use the sudo ufw status
command to view our rules.
Instead, we use the sudo ufw show added
command. This command will list all the rules we have added.
Always add the rules, review them, and then proceed to enable the firewall.
To enable UFW and apply the rules you’ve configured, use the following command:
sudo ufw enable
Now, you can check the status of UFW and your current ruleset using the sudo ufw status
command.
For a more detailed view:
sudo ufw status verbose
If you experience any issues, disable UFW using sudo ufw disable
and review your rules again.
If you need to reset UFW to its default state (removing all rules), you can use the sudo ufw reset
command.
Deleting Rules
If, for some reason, you want to delete a rule you have added, you can use the sudo ufw delete
command followed by the rule itself like this:
sudo ufw delete deny from 111.111.111.111 to any port 80 proto tcp
sudo ufw delete allow 80
There is another easier way to delete rules, but it requires the firewall to be enabled. This method involves using the rule number.
Once the firewall is enabled, you can use the sudo ufw status numbered
command to obtain a list of your rules and their corresponding numbers, like this:
To Action From
-- ------ ----
[ 1] 22/tcp ALLOW IN Anywhere
[ 2] 22/tcp (v6) ALLOW IN Anywhere (v6)
Now, to delete a rule, you can simply use the rule number:
sudo ufw delete 1
This is a much simpler method.
Best Practices
I want to share some best practices with you from my experience
The first step before enabling a firewall is to allow SSH traffic to ensure access to the server. If you enable the firewall before adding this rule, you risk losing access to your server.
I showed you how to use the allow
or limit
rules. Using these rules will permit any IP address to access port 22, which means our SSH port is open to everyone. This is something I avoid on a production server.
If I have a static IP from which I can access the server, I restrict the SSH port to that IP. This provides an extra layer of security and reduces the risk of unauthorized access.
Even if you’ve generated an SSH key pair, implemented key authentication, and created a non-root user, hackers could still attempt to breach your server.
If you’ve followed my guide on preparing Ubuntu servers, you should have already completed these security steps.
Only restrict the SSH port to your IP if it’s static – such as when using a VPN service or if your ISP has assigned you a static IP.
If you have a static IP, use the following command:
sudo ufw allow from <YOUR_IP> proto tcp to any port 22
Now, the IP specified in the command is the only one that can access the server.
Another useful feature to implement is a cloud firewall. Most server providers offer a cloud firewall, which allows you to set up a basic firewall at the cloud level and apply it to your servers.
With this, you can restrict the SSH port to your IP address using the cloud firewall while leaving it open to everyone in UFW. If your IP address changes, you can easily access your provider's dashboard to update it.
Now, let's talk about HTTP and HTTPS traffic.
If you're hosting a website, you need to allow traffic on ports 80 and 443 for visitors to access it. There should be no restrictions here, as we want everyone to reach the site.
However, there are a couple of considerations. If you're using an SSL certificate (which you should) and redirecting traffic from HTTP to HTTPS, there's no need to allow traffic on port 80. While allowing traffic on both ports is generally fine, I wanted to mention this.
Additionally, if you're using a proxy service like Cloudflare, traffic will only come from their IPs. To enhance security, restrict HTTP and HTTPS traffic to only Cloudflare's IPs.
For example, use these commands:
sudo ufw allow from <CF_IP> to any port 80 proto tcp
sudo ufw allow from <CF_IP> to any port 443 proto tcp
Repeat these commands for all Cloudflare IPs, or automate it with a bash script.
Advanced Firewall Rules
Up until now, we’ve focused on basic user-defined rules that you can easily manage from the command line.
Now, I’ll introduce the idea of advanced rules, which allow you to control traffic at a deeper level by configuring UFW's /etc/ufw/before.rules
files.
These advanced rules let you filter traffic before it reaches your server’s services and before the firewall applies its standard rules.
Advanced rules are incredibly powerful and can be tailored to specific use cases, offering finer control over your network's security and performance.
And as I mentioned earlier, all of UFW's rule files primarily use the iptables syntax.
Structure of before.rules
Files
The before.rules
file begins with a declaration of the *filter
table and defines several custom chains:
*filter
:ufw-before-input - [0:0]
:ufw-before-output - [0:0]
:ufw-before-forward - [0:0]
:ufw-not-local - [0:0]
:ufw-before-input
: Processes incoming packets.:ufw-before-output
: Processes outgoing packets.:ufw-before-forward
: Handles packets forwarded through the server.:ufw-not-local
: Deals with packets that are not addressed to or from the local system.
It includes several default rules to handle fundamental network operations and improve security. These rules cover a variety of scenarios, such as allowing all traffic on loopback interfaces or dropping invalid packets. They are applied by default once you enable the firewall.
The file ends with the COMMIT
line, signaling the completion of the rules.
The structure in before6.rules
for IPv6 is nearly identical to that of before.rules
for IPv4.
The main difference is that the chains in before6.rules
all have the number 6
added to their names, like this:
*filter
:ufw6-before-input - [0:0]
:ufw6-before-output - [0:0]
:ufw6-before-forward - [0:0]
However, unlike before.rules
, before6.rules
does not include a ufw6-not-local
chain.
It includes some of the default rules that before.rules
has, but it also contains additional rules that are applied specifically to IPv6 traffic.
The file also ends with the COMMIT
line, signaling the completion of the rules.
Use Cases
As I mentioned earlier, these files take priority, meaning they are executed first.
This allows us to define rules that will take effect before traffic reaches anything running on the server and before it travels further through the firewall.
You can, for example, implement a solution to block SYN flood attacks by rate-limiting the number of SYN packets allowed through ports 80 and 443, and block IPs exceeding these limits. This is something that cannot be done using basic user-defined rules from the command line.
While you can use the limit
rule for ports 80 and 443, it’s not ideal for a web server since the limits may not be well-suited to handle the typical traffic patterns of a web server.
Using more advanced rules in before.rules
allows you to fine-tune the firewall for specific use cases like this.
Example of Advanced Rules
If you examine the contents of the before.rules
file, you will notice these two rules:
-A ufw-before-input -m conntrack --ctstate INVALID -j ufw-logging-deny
-A ufw-before-input -m conntrack --ctstate INVALID -j DROP
These two rules are designed to log and block any invalid packets, and they are added by default by UFW to the ufw-before-input
chain, which filters incoming traffic before it reaches the server, ensuring that only legitimate connections are allowed.
UFW uses the conntrack
module (short for connection tracking) to monitor connections and identify those with INVALID
connection states. While these rules are effective, we can make them even better.
To further enhance the security of our server, we could add two additional rules to log and block any new connections that don’t have only the SYN flag set.
Add the following two rules below the ones added by default by UFW:
-A ufw-before-input -p tcp -m tcp ! --tcp-flags FIN,SYN,RST,ACK SYN -m conntrack --ctstate NEW -j ufw-logging-deny
-A ufw-before-input -p tcp -m tcp ! --tcp-flags FIN,SYN,RST,ACK SYN -m conntrack --ctstate NEW -j DROP
Don't forget to add them to the before6.rules
file as well:
-A ufw6-before-input -p tcp -m tcp ! --tcp-flags FIN,SYN,RST,ACK SYN -m conntrack --ctstate NEW -j ufw6-logging-deny
-A ufw6-before-input -p tcp -m tcp ! --tcp-flags FIN,SYN,RST,ACK SYN -m conntrack --ctstate NEW -j DROP
- The first rule drops any packet that’s considered
INVALID
by theconntrack
module. - The second rule blocks TCP packets that are flagged as
NEW
(indicating new connection attempts) but don’t have the SYN flag set alone.
Now, reload UFW if it is already enabled:
sudo ufw reload
These additional rules further enhance the firewall’s ability to filter out potentially malicious packets and protect your server from unwanted connection attempts.
Be sure to sign up for my newsletter to get notified about advanced firewall solutions, new rules, and other security tips as I develop them.
Conclusion and Final Thoughts
Great job reaching the end!
I hope this guide has made setting up a firewall with UFW clear and straightforward.
If you found value in this guide or have any questions or feedback, please don't hesitate to share your thoughts in the discussion section.
Your input is greatly appreciated, and you can also contact me directly if you prefer.
Discussion