Setting up a Linux firewall using UFW or iptables

Setting up a firewall (UFW / Iptables)

A firewall is a piece of software or hardware which filters network traffic coming in or out of a system. It's important to setup at least a software firewall on a server, so that you can control which applications can be publicly accessed, which can only be accessed from certain IP ranges, as well as blocking IPs / IP ranges which are causing problems for you.

For additional security tips, we recommend checking out our other article about securing your Linux server after you've finished setting up your firewall.


Looking to buy a Virtual or Dedicated server? Do you like privacy and low prices? Try Privex!

We have virtual servers starting from just US$0.99/mo, and dedicated servers starting from as low as US$50/mo

Unlike other hosts, we don't ask for any personal details - only a name (can be an alias / username), and an e-mail address so we can send you your server details and renewal invoices.

We also accept several different cryptocurrencies with our own in-house payment processor - no third parties involved in processing your payments.

At the time of writing, we currently accept: Bitcoin (BTC), Litecoin (LTC), Monero (XMR), Dogecoin (DOGE), HIVE, and HBD

Order a server TODAY! Privacy is affordable™


IMPORTANT: Become root

Managing UFW or IPTables involves a lot of commands which must be ran as root.

To avoid having to constantly prepend sudo to your commands, we strongly advise that you login as the root user, which you can generally do from the standard user that you login as (e.g. ubuntu / debian user) by running this command:

sudo su -

Your shell prompt should now show that you are root:

root@myserver:~ #

You can now run commands that require root, without needing to prepend sudo

If for some reason, you don't want to become the root user, or your specific setup does not allow you to have a root shell and you need to run everything via sudo, then you'll need to remember to prepend sudo to most commands listed in this section

Using UFW

UFW is one of the most user friendly options for firewalling your server, but may hold back advanced system administrators who want to setup complex firewall rules.

If you require advanced / complex firewall rules, you may prefer using IPTables directly, rather than UFW.

For most standard server usecases however, UFW should be perfectly fine.

Installing UFW

UFW may or may not be already installed on your system, it doesn't hurt to run the install commands to check though, and this will also update UFW if it's out of date.

Ubuntu / Debian based distros:

sudo apt update
sudo apt install ufw

Fedora / CentOS / RHEL based distros:

sudo dnf install ufw

# On older versions of Fedora/CentOS/RHEL
# you may need to use yum instead of dnf
sudo yum install ufw

Note on SSH port change

If you've changed your SSH port, you'll want to update the OpenSSH UFW profile with your new port number.

If you haven't changed your SSH port, you can skip this.

(Please login as root via sudo su - as mentioned earlier in this guide before running commands)

First you'll need to open the profile in a text editor such as nano:

nano /etc/ufw/applications.d/openssh-server

Inside this file, you should see something like this:

[OpenSSH]
title=Secure shell server, an rshd replacement
description=OpenSSH is a free implementation of the Secure Shell protocol.
ports=22/tcp

You'll need to change the ports=22/tcp line, replacing 22 with the port you changed SSH to use.

For example, if your SSH port is 2222, you'd change the ports line to ports=2222/tcp - so the final file may look like:

[OpenSSH]
title=Secure shell server, an rshd replacement
description=OpenSSH is a free implementation of the Secure Shell protocol.
ports=2222/tcp

Assuming you're using nano to edit it, press CTRL-X to exit, press Y when it asks if you'd like to save the file, and then hit enter to accept the default save path.

To confirm the file was properly updated, we can use cat to view the contents of the file:

cat /etc/ufw/applications.d/openssh-server

Now we need to run the following command to reload the UFW profile:

ufw app update OpenSSH

Then we can check to make sure it's updated correctly by running the following command to view the live profile:

ufw app info OpenSSH

It should show the updated ports at the bottom:

Profile: OpenSSH
Title: Secure shell server, an rshd replacement
Description: OpenSSH is a free implementation of the Secure Shell protocol.

Port:
  2222/tcp

Now you've successfully updated your OpenSSH UFW profile, and can continue with the guide :)

Allow SSH + enable UFW

Now that UFW is installed, and you've adjusted the SSH UFW profile (if needed), we can start using it!

(Please login as root via sudo su - as mentioned earlier in this guide before running commands)

First, we want to make sure SSH is whitelisted so that you don't lose SSH access when it's turned on:

ufw allow OpenSSH

It should display Rules updated if it worked correctly. (If it shows an error, please look up the error on a search engine to check what you should do)

If there are any other important services that you need to ensure have their port open and don't get disrupted, you can use the following command to allow their ports:

# Assuming the service is on port 5555, you'd run the following command:
ufw allow 5555

Now let's enable the firewall:

ufw enable

It will warn you that enabling the firewall may disrupt SSH - assuming you've correctly allowed OpenSSH in the previous steps, your SSH should be unaffected, so you can hit Y to continue:

root@ufw-test:~# ufw enable
Command may disrupt existing ssh connections. Proceed with operation (y|n)? y
Firewall is active and enabled on system startup

To check the firewall is enabled and which rules are in the firewall, you can use ufw status like so:

root@ufw-test:~# ufw status
Status: active

To                         Action      From
--                         ------      ----
OpenSSH                    ALLOW       Anywhere
5555                       ALLOW       Anywhere
OpenSSH (v6)               ALLOW       Anywhere (v6)
5555 (v6)                  ALLOW       Anywhere (v6)

You should now have a basic firewall setup, which will allow traffic to your SSH port, and any other applications which you have allowed their port.

Verbose status and numbered status report

You can get a more detailed report from UFW using ufw status verbose which will show you the default input/output policy for traffic without rules, whether logging is enabled, and will show ports for application profiles, rather than just their name:

root@ufw-test:~# ufw status verbose
Status: active
Logging: on (low)
Default: deny (incoming), allow (outgoing), disabled (routed)
New profiles: skip

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

To assist in removing/ordering firewall rules, you can request a numbered list of rules, so that you can see the number of each rule:

root@ufw-test:~# ufw status numbered
Status: active

     To                         Action      From
     --                         ------      ----
[ 1] OpenSSH                    ALLOW IN    Anywhere
[ 2] 5555                       ALLOW IN    Anywhere
[ 3] OpenSSH (v6)               ALLOW IN    Anywhere (v6)
[ 4] 5555 (v6)                  ALLOW IN    Anywhere (v6)

Removing firewall rules

There are two common ways you can remove a firewall rule from UFW:

  • Using the rule number
  • Using the rule policy

Generally removing via rule number is going to be the most accurate and easiest.

Removing a rule via rule number

First you need to find the rule number, which you can do using ufw status numbered

root@ufw-test:~# ufw status numbered
Status: active

     To                         Action      From
     --                         ------      ----
[ 1] OpenSSH                    ALLOW IN    Anywhere
[ 2] 5555                       ALLOW IN    Anywhere
[ 3] OpenSSH (v6)               ALLOW IN    Anywhere (v6)
[ 4] 5555 (v6)                  ALLOW IN    Anywhere (v6)

Assuming this is the rule we want to remove:

[ 4] 5555 (v6)                  ALLOW IN    Anywhere (v6)

We can see that it has the rule number 4, so we can remove it using ufw delete 4:

root@ufw-test:~# ufw delete 4
Deleting:
 allow 5555
Proceed with operation (y|n)? y
Rule deleted (v6)

If we run ufw status numbered again - we can see that rule #4 has been deleted, meaning we have removed the rule that allowed port 5555 for IPv6, without touching rule #2 which allows 5555 for IPv4:

root@ufw-test:~# ufw status numbered
Status: active

     To                         Action      From
     --                         ------      ----
[ 1] OpenSSH                    ALLOW IN    Anywhere
[ 2] 5555                       ALLOW IN    Anywhere
[ 3] OpenSSH (v6)               ALLOW IN    Anywhere (v6)

Removing a rule via rule policy

You can remove a rule based on it's policy, which can be useful for deleting multiple rules.

First we'll check what rules we currently have using ufw status verbose:

root@ufw-test:~# ufw status verbose
Status: active
Logging: on (low)
Default: deny (incoming), allow (outgoing), disabled (routed)
New profiles: skip

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

Assuming we want to get rid of the rules which allow traffic to port 5555, we can delete them similar to how we originally made them, using ufw delete allow 5555:

root@ufw-test:~# ufw delete allow 5555
Rule deleted
Rule deleted (v6)

If we run ufw status verbose again - you'll see that BOTH the IPv4 and IPv6 rules that allowed port 5555 are now gone:

root@ufw-test:~# ufw status verbose
Status: active
Logging: on (low)
Default: deny (incoming), allow (outgoing), disabled (routed)
New profiles: skip

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

Adding more advanced rules

Allowing traffic FROM a certain IP or subnet

To allow all traffic from the IP address 10.1.2.3 (i.e. to any port) - run the following command:

ufw allow from 10.1.2.3

We can do the same for IPv6 addresses, such as 2a07:e00::333:

ufw allow from 2a07:e00::333

If you need to allow an entire subnet, it works the same way, just add the subnet mask:

# Allow all traffic from 10.1.0.0 to 10.1.0.255
ufw allow from 10.1.0.0/24

If you want to allow an IP address to only be able to access a certain port:

# Allows the IP address 10.1.2.3 to access port 4444
ufw allow from 10.1.2.3 to any port 4444

Blocking traffic FROM a certain IP or subnet

You can block traffic from certain IP addresses or subnets in much the same way as you allow them.

To block all traffic from the IP address 10.3.2.5:

ufw deny from 10.3.2.5

To block all traffic from the IPv6 address 2a07:e02:ab:cd:463::8af

ufw deny from 2a07:e02:ab:cd:463::8af

To block all traffic from IPs in the range 10.3.0.0 to 10.3.255.255:

ufw deny from 10.3.0.0/16

To block traffic from a certain IP only for a certain port:

# Block traffic from 10.3.2.4 only if it's going to port 3333
ufw deny from 10.3.2.4 to any port 3333

Block OUTGOING traffic to a certain port or IP

In some cases, you may want to block traffic that's outgoing from your server, rather than traffic coming into your server, such as to prevent an employee/customer's traffic going through the server accessing things you don't want them to.

For example, to block traffic going to any IP on port 25 (SMTP), to help prevent applications from sending spam:

ufw deny out 25

To block traffic going to a specific IP address:

# Block any traffic going to 10.4.4.4
ufw deny out to 10.4.4.4

To block traffic only to a specific port on a specific IPv6 subnet:

# Block traffic going to port 1234 in the subnet 2a07:e02:123:123::/64
ufw deny out to 2a07:e02:123:123::/64 port 1234

Using IPTables

For some advanced system adminstrators, UFW might not support the advanced firewall rules they'd like to configure.

It's possible to use a mixture of UFW and manual IPTables rules, however some administrators may prefer having full control over their firewall rules.

Setting and viewing basic iptables firewall rules in memory

In situations where you need to immediately apply a firewall rule, without it being persisted, you can use the commands iptables and ip6tables to set IPTables rules in memory, which you can later copy to your persistent IPTables rule file if desired.

Allow already established connections, ICMP, and loopback traffic

Allowing established connections is a very important rule and should be set before changing your global policy to DROP/REJECT - as it tells the firewall not to filter connections that are already established (such as your SSH connection that you're managing it from).

You'll also want to whitelist traffic to the interface lo (loopback), which is internal network traffic such as to 127.0.0.1 / ::1

You may also want to whitelist ICMP, so that you can still ping / traceroute your server

The following rules do all of the above, for both IPv4 and IPv6:

iptables -I INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
iptables -I INPUT -p icmp -j ACCEPT
iptables -I INPUT -i lo -j ACCEPT
ip6tables -I INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
ip6tables -I INPUT -p ipv6-icmp -j ACCEPT
ip6tables -I INPUT -i lo -j ACCEPT

Allow all incoming traffic to a given port

To allow all TCP traffic to port 22 (SSH) using iptables:

# Use iptables for IPv4 rules
iptables -A INPUT -p tcp --dport 22 -j ACCEPT

# Use ip6tables for IPv6 rules
ip6tables -A INPUT -p tcp --dport 22 -j ACCEPT

Block all incoming traffic to a given port or from a given IP

To block all TCP and UDP traffic to port 3333 using iptables:

iptables -A INPUT -p tcp --dport 3333 -j DROP
iptables -A INPUT -p udp --dport 3333 -j DROP
ip6tables -A INPUT -p tcp --dport 3333 -j DROP
ip6tables -A INPUT -p udp --dport 3333 -j DROP

To block all traffic coming from the IPv4 address 10.4.5.6:

iptables -A INPUT -s 10.4.5.6 -j DROP

To block all traffic coming from the IPv6 subnet 2a07:e02:abc:dff::/64:

ip6tables -A INPUT -s 2a07:e02:abc:dff::/64 -j DROP

Allow traffic from a given IP address or subnet

To allow all traffic from the IP address 10.1.2.3 you use the following command - note that you should use -I instead of -A to make sure the rule is inserted at the start of the rules table, so that it's not ignored due to a DROP/REJECT rule earlier on:

iptables -I INPUT -s 10.1.2.3 -j ACCEPT

To allow traffic from the subnet 10.4.0.0/24 but only to the TCP ports 2222,3333,4444:

iptables -I INPUT -s 10.4.0.0/24 -p tcp -m multiport --dports 2222,3333,4444 -j ACCEPT

To allow all traffic from the IPv6 subnet 2a07:e00::/32 we need to use ip6tables instead of iptables:

ip6tables -I INPUT -s 2a07:e00::/32 -j ACCEPT

View your iptables rules

To view all your rules in memory, you can use iptables-save and ip6tables-save:

# Prints out all current IPv4 rules
iptables-save
# Prints out all current IPv6 rules
ip6tables-save

Setting the global policy for incoming traffic which doesn't have a rule

Once you have your most important rules setup, such as allowing established connections and SSH traffic, you can change the global policy so that traffic that doesn't match any rules is automatically rejected.

To drop any incoming traffic that doesn't match your rules:

iptables -P INPUT DROP
ip6tables -P INPUT DROP

You may wish to also DROP any FORWARD traffic, however be aware that if you use this server for virtual machines or network routing (such as VPNs, or have machines pointed to the server as their internet gateway), you'll need to setup appropriate FORWARD rules to avoid breaking their connectivity:

iptables -P FORWARD DROP
ip6tables -P FORWARD DROP

Persistent IPTables rules

To persist your IPTables rules, you'll need to install a service which handles persistent IPTables rules such as netfilter-persistent (formerly called iptables-persistent)

On Ubuntu/Debian based systems, this package is available as netfilter-persistent:

apt update
apt install netfilter-persistent

Once it's installed, you can generate the IPTables persistent configuration files by simply dumping your existing IPTables rules from memory:

# Dump your IPv4 iptables rules
iptables-save > /etc/iptables/rules.v4
# Dump your IPv6 iptables rules
ip6tables-save > /etc/iptables/rules.v6

You can now either edit the rules files directly as needed at /etc/iptables/rules.v4 for IPv4 rules, and /etc/iptables/rules.v6 for IPv6 rules - or you can simply dump your in-memory rules whenever you want to update the persistent rules.

Make sure the service is enabled (so it starts on boot) and running:

systemctl enable netfilter-persistent
systemctl restart netfilter-persistent

How do the rules files work?

The persistent rule files work similar to creating rules in memory, you'll see that each section of the rule file contains lines which look like the command arguments for iptables / ip6tables

For example, it may look like this:

*filter
:INPUT DROP [0:0]
:FORWARD DROP [0:0]
:OUTPUT ACCEPT [0:0]
:f2b-sshd - [0:0]

-A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
-A INPUT -p icmp -j ACCEPT
-A INPUT -i lo -j ACCEPT
-A INPUT -p tcp --dport 22 -j ACCEPT

COMMIT

As you can see, outside of the table configuration blocks (the area with :INPUT DROP [0:0] etc.), the file simply contains IPTables command line arguments, and you can enter rules in here as you would via the iptables command - just without iptables at the start.

Reboot to test it worked

To make sure netfilter-persistent is working correctly, after you've setup your rules files with some IPTables rules, we recommend rebooting your server to make sure that it's correctly loading your rules on boot.

To reboot a Linux server, simply run the following command:

sudo reboot

Wait for the server to come back up, and then run:

sudo iptables-save

sudo ip6tables-save

The output should look like your rules.v4 / rules.v6 configuration, possibly plus a few extra rules automatically added by applications such as fail2ban.

If so, you've now got a working IPTables that persists between reboots! Congratulations!

Please remember that when you run iptables / ip6table to add rules in memory, that they will not automatically persist. You'll need to remember to either edit the rules files yourself to add the rules you'd like to persist, or use iptables-save / ip6tables-save to dump your rules into the rules files as previously shown.

Extra Security Tips

For additional security tips, we recommend checking out our other article about securing your Linux server after you've finished setting up your firewall.


Looking to buy a Virtual or Dedicated server? Do you like privacy and low prices? Try Privex!

We have virtual servers starting from just US$0.99/mo, and dedicated servers starting from as low as US$50/mo

Unlike other hosts, we don't ask for any personal details - only a name (can be an alias / username), and an e-mail address so we can send you your server details and renewal invoices.

We also accept several different cryptocurrencies with our own in-house payment processor - no third parties involved in processing your payments.

At the time of writing, we currently accept: Bitcoin (BTC), Litecoin (LTC), Monero (XMR), Dogecoin (DOGE), HIVE, and HBD

Order a server TODAY! Privacy is affordable™