Introduction
Many of you have probably used Iptables before and probably found it quite cumbersome:
- Hard to read rules
- No really streamlined way to persist rules across reboots
- Generally a mess to script with, using shell scripts
Well, turns out there is a great successor to Iptables! It's pretty simple to use and surprisingly few people know about it: nftables, short "nft" (that abbreviation really hasn't aged well...)!
In this tutorial we'll be setting up a simple firewall allowing all ports outgoing as well as HTTP, HTTPS, SSH and DNS incoming.
Prerequisites
-
All you need is any Linux distro that provides nftables. That is pretty much any Linux distro that has been updated within the last 10 years.
On Debian and Ubuntu the package is called
nftables
:sudo apt install nftables
-
You should also make sure no frontends like UFW are currently in use. For UFW, run this command to make sure:
sudo ufw disable
You can use nft --version
and sudo ufw status
to check their status.
Example terminology
- Main network interface:
enp5s0
Step 1 - Opening the configuration file
Most distros have either /etc/nftables.conf
or /etc/sysconfig/nftables.conf
. Whichever one exists on your system, open it in your favorite editor:
sudo editor /etc/nftables.conf
Step 2 - Deleting the skeleton
The file may either be empty or look something like this:
#!/usr/sbin/nft -f
flush ruleset
table inet filter {
chain input {
type filter hook input priority filter;
}
chain forward {
type filter hook forward priority filter;
}
chain output {
type filter hook output priority filter;
}
}
If this is the case, clear the file before continuing.
Otherwise STOP: You are going to have to make sure to carry over any existing rules. One way could be to not clear the file but to append instead (but that could potentially be problematic). Handling this case is out of scope of this tutorial.
Important note: Do not delete any important existing rules (for virtualization, for example).
Step 3 - Writing the firewall configuration
Here's a template. You can go ahead and paste it into the config file:
Replace the example interface and custom settings as explained in "Step 3.1".
#!/usr/sbin/nft -f
# Variables
define main_interface = "enp5s0"
# Delete previous table
table ip my_filter
delete table ip my_filter
# Create new table
table ip my_filter {
# Filter ingoing traffic
chain input {
type filter hook input priority 0;
iifname $main_interface tcp dport {22, 80, 443} accept
iifname $main_interface udp dport 53 accept
iifname $main_interface ip protocol icmp accept # Accept all ICMP traffic
iifname $main_interface ct state established,related accept # Accept any input traffic originated from us
iifname $main_interface ct state invalid drop # Drop invalid packets...
iifname $main_interface icmpv6 type {echo-request,nd-neighbor-solicit} accept # Accept IPv6 neighbour discovery
iifname $main_interface drop # Drop everything else
}
# Drop all packages to be forwarded (we're not a gateway!)
chain forward {
type filter hook forward priority 0; policy drop;
}
# Allow all outgoing traffic
chain output {
type filter hook output priority 0; policy accept;
}
}
Step 3.1 - Customize the configuration file
Make sure to change enp5s0
to your public-facing network interface. If you have multiple, you can specify it like so:
define main_interface = {"enp5s0", "enp7s0"}
Now just add whatever ports you'd like to be reachable to those lines:
iifname $main_interface tcp dport {22, 80, 443} accept
iifname $main_interface udp dport 53 accept
Note that you must only use {}
when specifying multiple ports. Alternatively, if you don't need any UDP ports accessible you can just omit the whole line.
You can also specify port ranges like this:
iifname $main_interface udp dport {53, 1000-1999} accept
Tip: Chains with lower priority are processed first.
Step 3.2 - Allow forwarding to bridges (Optional)
In some cases, you'd have bridge networking set up for virtual machines. In this case, you'd have to allow forwarding to those bridges.
To do this, simply add the following rule to the end of the forward
chain, replacing br0
with your bridge interface for each:
iifname $main_interface oifname "br0" accept
And remove the comment above the chain.
Step 4 - (Re)load the firewall rules
Now that we are ready to apply our new rules, just mark the configuration file as executable (only needed once):
sudo chmod a+x /etc/nftables.conf
And simply execute it like:
sudo /etc/nftables.conf
You can repeat this step any time in the future you'd like to change the rules. They are also going to be applied automatically when rebooting.
Step 5 - Troubleshooting
-
I can't connect anymore!
That sucks, but it's fixable! If your server is a cloud server, you can log in via the cloud web interface and run
sudo nft delete table ip my_filter
to disable the firewall temporarily.If your server is a dedicated server, you could either order a KVM (if you don't want to reboot the server) and run the command above, or you could reboot into the rescue system via the Hetzner Robot web interface and manually delete the firewall configuration.
You've probably just forgotten to allow incoming connections on your SSH port!
-
The firewall doesn't work
If the configuration applies without an error but has no effect, please make sure you've changed
main_interface
to the public-facing network interface:define main_interface = "enp5s0"
Conclusion
Now you have a nice and modern Firewall set up using nftables! There is also a nftables wiki available that may be worth a read.