Setting up a firewall is essential for securing your server, and UFW simplifies the process, making it user-friendly.

Think of it as your server’s bouncer – it filters incoming and outgoing traffic, allowing only authorized access to specific ports.

In this guide, I'll walk you through setting up a firewall with UFW, along with best practices to ensure effective firewall management.

Preparation

To make the most of this guide, ensure you have a properly set up Ubuntu server.

👉
Check out my guide on preparing Ubuntu servers to ensure your server is properly set up.

If you don’t have one, consider getting a free VPS server to follow along.

Installation

On Debian-based distributions, like Ubuntu, it 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.

👉
New to Hetzner? Use my link to get free credits!

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.

If you wish to review the rules you added and your firewall is disabled, the sudo ufw status command won’t display your ruleset.

Instead, you can use the sudo ufw show added command to view the rules you added, even when the firewall is disabled.

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.

If you wish to review the default settings for UFW, you can examine the /etc/default/ufw file.

Open the file using your preferred editor.

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

You can review the default policy using the following command too:

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 denyallow 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.

Checking Open Ports

Before adding any firewall rules, you need to identify which ports are open on your server.

This information can be obtained using the nmap utility.

Install the nmap package using the following command:

sudo apt install nmap

Once nmap is installed, use the following command to scan for open ports:

sudo nmap <server_ip_address>

Output:

PORT    STATE SERVICE
22/tcp  open  ssh
25/tcp  open  smtp
80/tcp  open  http
110/tcp open  pop3
143/tcp open  imap
587/tcp open  submission
993/tcp open  imaps
995/tcp open  pop3s

This is the output I got from one of my servers.

If your server is new, you might only see the SSH port open, as SSH is the only service installed by default.

By default, Nmap only scans TCP ports. Use the following command to scan UDP ports:

sudo nmap -sU <server_ip_address>

The open state, as indicated, isn’t solely related to the firewall.

In other words, it doesn’t necessarily imply that the port is accessible to the public. Even if I have a rule to deny traffic to port 80, Nmap may still show the port as open.

Essentially, this signifies that a service, such as the HTTP service, is installed on the server and is using port 80. We utilize nmap to identify the services installed on our server along with their associated ports, helping us plan the necessary rules to add.

Now that we’ve identified the open ports on our server using nmap and understand that UFW defaults to blocking incoming traffic, the next step is to add rules that allow traffic to these ports.

SSH Traffic

The first step before enabling a firewall is to allow traffic on port 22 (SSH) to ensure access to the server.

If you enable the firewall before adding this rule, you risk losing access to your server.

Use the following command to allow SSH traffic:

sudo ufw allow 22/tcp

This is a simple and swift solution.

Essentially, we’ve permitted any IP address to access port 22, meaning our SSH port is open to everyone.

This is something I never do on a production server.

Even if you have generated an SSH key pair, implemented key authentication, and added a non-root user, hackers could still attempt unauthorized access.

🙆‍♂️
If you’ve followed my guide on preparing Ubuntu servers, you should have already completed these steps.

What if we could proactively prevent these attempts? This would enhance overall security.

We can restrict the SSH port to a specific IP, allowing only this designated IP to access it. This ensures that SSH access is limited to a single, trusted IP address, such as your IP at your home network.

However, this approach comes with a drawback: Most home networks use DHCP, causing your IP to change periodically.

When your IP changes, you lose access to the server, making it less practical.

I typically opt for restricting the SSH port to a specific IP only when I have a dedicated static IP.

A method to acquire a dedicated static IP is by using a VPN service. Numerous VPN providers provide the option of a dedicated static IP for an additional fee.

If you have a dedicated static IP, use the following command:

sudo ufw allow from <ip> proto tcp to any port 22

Now, the IP specified in the command is the only one that can access the server.

💡
When you restrict SSH access to only one IP, Fail2ban becomes irrelevant as there are no IPs to block. I mention this to avoid confusion. However, I still recommend keeping Fail2ban installed and enabled even when restricting SSH access.

If I can’t restrict SSH access and don’t want to make my SSH port accessible to all, there’s still something I can do.

Use the limit rule instead of the allow rule for SSH on a production server:

sudo ufw limit 22/tcp

It allows only 6 connections from the same IP address within a 30-second window, protecting the server from potential brute-force attacks.

HTTP & HTTPS Traffic

If you are hosting a website, you need to allow traffic on ports 80 (HTTP) and 443 (HTTPS) for your visitors to reach your website.

In this case, there should be no restrictions, as we want everyone to be able to access our website.

You can allow traffic by using the allow rule, similar to what we did for SSH, as follows:

sudo ufw allow 80/tcp
sudo ufw allow 443/tcp

This should be fine, but there are some considerations I would like to share with you.

First, if you are using an SSL certificate, which you should, and redirecting traffic from HTTP to HTTPS, as recommended, there is no need to allow traffic on port 80. Although allowing traffic on both ports is not a problem and I’ve never faced any issues with it, I wanted to inform you.

Second, if you are using a proxy service, such as Cloudflare, traffic will only come from their IPs. You can enhance security by restricting HTTP and HTTPS traffic to only their IPs.

For example, for Cloudflare, you would 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

You should repeat these two commands for all Cloudflare IPs, or you can use a bash script to automate this.

Other Traffic

Now that you know how to allow traffic, restrict access, and utilize the limit rule, it’s time to add rules for other ports.

Plan your ruleset based on the services installed and those you intend to use.

If you have a control panel installed on your server or a service that you exclusively use, consider restricting access to your IP or using the limit rule.

Denying Traffic

As I mentioned earlier, UFW is configured to deny all incoming traffic by default.

However, there may be situations where you want to block traffic based on the source IP address or subnet, especially if you’re aware of attacks coming from that source.

Consider this simple scenario: I have a WordPress website hosted on my server, and I’ve set rules to allow traffic on ports 80 and 443. However, I noticed that a specific IP is continuously attacking the login page, consuming my server resources. To address this, I want to block traffic from that IP, preventing access to the website.

I can achieve this using the following commands:

sudo ufw deny from <ip> to any port 80 proto tcp
sudo ufw deny from <ip> to any port 443 proto tcp

This way, we have blocked HTTP and HTTPS traffic to our website from that IP address.

If you want to block traffic from an IP address on all ports, you can use the following command:

sudo ufw deny from <ip>

If an entire subnet of IPs is causing issues, we can extend this approach to block traffic from the entire subnet using this command:

sudo ufw deny from <ip>/24

These are some basic examples of how to deny traffic from a specific source.

These can be quite handy in certain situations when you need to quickly stop an ongoing attack.

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

Output:

Available applications:
  Apache
  Apache Full
  Apache Secure
  Dovecot IMAP
  Dovecot POP3
  Dovecot Secure IMAP
  Dovecot Secure POP3
  Nginx Full
  Nginx HTTP
  Nginx HTTPS
  OpenSSH
  Postfix
  Postfix SMTPS
  Postfix Submission

This is the output I got from one of my servers.

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 utilize the profile name to allow, deny, or reject traffic.

For instance, to allow traffic on port 443 (HTTPS), you would use the following command:

sudo ufw allow "NGINX HTTPS"

Or:

sudo ufw allow "Apache Secure"

If you’re curious about the origins of these profiles, check the /etc/ufw/applications.d/ directory.

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

Or:

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:

Output:

Status: active

To                         Action      From
--                         ------      ----
[ 1] 22/tcp                ALLOW IN    Anywhere                  
[ 2] 22/tcp (v6)           ALLOW IN    Anywhere (v6)  

Now, to delete a rule, I can simply use the rule number:

sudo ufw delete 1 

This is a much simpler method.

Pre-Activation Check

Before activating our firewall, it’s crucial to review the rules we’ve added so far to prevent any unexpected behavior.

Since the firewall is currently disabled, we can’t use the sudo ufw status command to get a list of our rules.

Instead, we could use this command:

sudo ufw show added

This command will list all the rules you have added.

Always add the rules, review them, and then proceed to activate the firewall.

Activation

For our final step, let’s enable the firewall.

Simply use this command:

sudo ufw enable

With this, our firewall is enabled.

If you experience any issues, review your rules again.

Conclusion and Final Thoughts

Great job reaching the end!

I hope this guide has made setting up a firewall with UFW clear and straightforward.

Implementing a firewall is a critical step in securing your server, and following best practices helps ensure you have effective control over network traffic.

🙆‍♂️
There's more to securing Linux servers. I’ve created a comprehensive guide on server security and hardening, where I share the exact security measures I implement on my own servers.

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.