Firewalls: How to setup a basic firewall using UFW, iptables, nftables, or firewalld

This article will be covering how to setup a basic firewall using each of the four main methods of doing so. I’ll leave covering advanced stuff to James, as he’s been somewhat obsessed with learning the complicated aspects of firewalls recently, but the basic are also important.

UFW

I’ll start with the simplest one. UFW is a simple frontend to iptables that makes it easy to block and allow ports and have persistence across reboots. Most major distributions have it available in their standard repositories of packages.

So to start off, installing the packages is usually done with some variation of:

apt install ufw
pacman -S ufw


Or whatever applies to your distro. Once the install finishes, we can start making rules.

As I said above, this is just going to be a basic firewall, so no fancy forwarding or even logging. Just blocking and allowing ports.

ufw default deny
ufw allow ssh


The first command will block all incoming traffic by default, as well as set forwarded traffic to deny. The second will allow inbound SSH traffic, so that when we turn the firewall on we will still have access.

If needed, you can also specify a protocol for UFW to allow in. For example, pretending this is a DNS server:

ufw allow 53/udp


Once you are happy with the ports that you have allowed (make sure that you will still have access!), you can start the firewall with:

ufw enable
ufw status verbose


And ta-da, you have a basic UFW firewall!

firewalld

firewalld is Red Hat’s baby, and is kinda like UFW on steroids. It is also a frontend to nftables or iptables, but is significantly more powerful and can handle multiple rulesets. For example, it allows you to have different rules for different nics, as well as runtime and permanent configs (think Cisco IOS and having to do “commit; save” in order for rules to survive reboots).

If it is not pre-installed on your server, it can usually be installed as the firewalld package. For example on Arch:

pacman -S firewalld


And then the service will have to be started and enabled:

systemctl start firewalld
systemctl enable firewalld
firewall-cmd --state


As I said earlier, this is just going to be a basic overview of each option. So I won’t be going over how to having different zones for each interface and such. Just one zone on one interface.

To start with, to set the default policy to drop all traffic that doesn’t match our allow rules, we can set the default zone to drop:

firewall-cmd --set-default-zone=drop


This will make the change immediately and permanently, but established connections will be maintained. And now to allow future SSH traffic into the machine, we can do:

firewall-cmd --zone=drop --permanent --add-service=ssh


The --permanent flag will make the change persist across reboots, removing it will have the change only be present until firewalld is reloaded.

Now, going back to the DNS server example used above, you can specify a port and protocol to allow using a command like this:

firewall-cmd --zone=drop --add-port=53/udp


And like when adding a service, including a --permanent will make the change persistent.

With that, you have a basic firewalld firewall. firewalld do can do way more than just this if you dig deeper into its documentation, though! So it could be worth it to do so.

iptables

The programs so far have both been frontends to iptables that aim to make it easier to interact with. But by doing so, some of the power granted by iptables is lost. However, that is the sort of thing I’ll leave to James to cover some day.

It is very likely that iptables is already installed on your system. If not, install the “iptables” package. You do not need to start a service for it, but you will potentially need to enable one. For now, we will set rules up. To make sure that we won’t lose connection to the box, first allow established traffic:

iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT


And now we can safely set the default policy to DROP, like so:

iptables -P INPUT DROP


This will block all traffic by default, but rather than informing the client it was denied, the server will allow the connection to timeout. And now to allow traffic in we can do:

iptables -A INPUT -p tcp --dport 22 -j ACCEPT
iptables -A INPUT -p udp --dport 53 -j ACCEPT


That will allow incoming tcp traffic to port 22 and udp traffic to port 53.

Now to make these rules persist. On systems with OpenRC, you can usually do something like follows:

rc-update add iptables default
service iptables save


And on systemd machines:

systemctl enable iptables
iptables-save > /etc/iptables/iptables.rules


Both examples will write the rules to a file and then load them back in on reboot. If you use systemd and do not have an iptables.service file, here is what mine looks like:

[Unit] 
Description=IPv4 Packet Filtering Framework
Before=network-pre.target
Wants=network-pre.target

[Service]
Type=oneshot
ExecStart=/usr/bin/iptables-restore /etc/iptables/iptables.rules
ExecReload=/usr/bin/iptables-restore /etc/iptables/iptables.rules
ExecStop=/usr/lib/systemd/scripts/iptables-flush
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target


Save that as /usr/lib/systemd/system/iptables.service, and then enable the service. The iptables-flush script it references looks like this:

#!/bin/bash 
#
# Usage: iptables-flush [6]
#

iptables=ip$1tables
if ! type -p "$iptables" &>/dev/null; then
 echo "error: invalid argument"
 exit 1
fi

while read -r table; do
 tables+=("/usr/share/iptables/empty-$table.rules")
done <"/proc/net/ip$1_tables_names"

if (( ${#tables[*]} )); then
 cat "${tables[@]}" | "$iptables-restore"
fi


With that done, you should have a functioning iptables firewall!

nftables


iptables has been deprecated for a while now, and nftables is its horribly documented successor. Hopefully this remedies that a little bit.

There is a chance that nftables is already installed on your system, but if not the package is usually just called “nftables“.

First, start and enable the service:

systemctl enable nftables
systemctl start nftables


The starting ruleset may differ a little by OS, so nuke it:

nft flush ruleset
nft add table inet filter


After doing that, we will need to add our chains. For now the default policy for incoming traffic will be accept, otherwise we’ll lock ourselves out. To add our default chains, the commands are:

nft add chain inet filter input { type filter hook input priority 0\; policy accept\; }
nft add chain inet filter forward { type filter hook forward priority 0\; policy drop\; }
nft add chain inet filter output { type filter hook output priority 0\; policy accept\; }


The backslashes are required. Without them your shell will try to interpret this as listing several commands. Also, I’ve found that zsh requires that the {} be escaped as well.

And now to add the allowed ports., as well as traffic related to connections on those ports. That can be done like so:

nft add rule inet filter input ct state { established, related } accept
nft add rule inet filter input tcp dport ssh accept
nft add rule inet filter input udp dport 53 accept


Finally, to set the default policy to drop incoming traffic:

nft chain inet filter input { type filter hook input priority 0\; policy drop\; }


Running nft list ruleset will display the current rules, which should look something like this:

Current nftables firewall rules
nft list ruleset


With that, you should have a functioning nftables firewall. To have it persist across reboots, the method is similar to iptables. With systemctl, it will usually read from /etc/nftables.conf.

nft list ruleset > /etc/nftables.conf
systemctl enable nftables
systemctl start nftables


About: Bailey Kasin

I build virtual environments and challenges for Cybersecurity students to complete as a way to gain experience before graduating and entering the workforce.


One thought on “Firewalls: How to setup a basic firewall using UFW, iptables, nftables, or firewalld”

Leave a Reply

Your email address will not be published. Required fields are marked *