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.


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


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:

Description=IPv4 Packet Filtering Framework

ExecStart=/usr/bin/iptables-restore /etc/iptables/iptables.rules
ExecReload=/usr/bin/iptables-restore /etc/iptables/iptables.rules


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

# Usage: iptables-flush [6]

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

while read -r table; do
done <"/proc/net/ip$1_tables_names"

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

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


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.

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

  1. I have been using only nftables for webservers, is it enough or shouldnI use also smt else? How to block by path based? Like allow only https traffic which contains “admin” in the path from certain IPs?

    1. nftables is a great solution for a host based firewall. But the functionality that I think you’re looking for would need something like ModSecurity.

Leave a Reply

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