nftables is the modern Linux packet filter and the framework that UFW, firewalld and today's iptables command all write to. Using it directly gives you one readable config file that covers IPv4 and IPv6 together. This guide explains the building blocks, then builds a small, safe server firewall: drop everything incoming except SSH, HTTP and HTTPS.
Before you start
- A Linux VPS running Ubuntu 22.04 or 24.04, Debian 12, or AlmaLinux or Rocky Linux 8 or 9.
- Root or a sudo user.
- No other firewall manager active. If UFW or firewalld is running, use that tool instead, or disable it first. See UFW vs firewalld vs nftables vs iptables.
- Your SSH port. It is 22 unless you changed it.
- Access to the browser console in your VPS provider's control panel.
Install the nft tool if it is missing:
# Ubuntu and Debian
sudo apt install nftables
# AlmaLinux and Rocky Linux
sudo dnf install nftablesWarning: A default-drop input chain blocks everything you do not allow. Always allow your SSH port in the same ruleset that sets the drop policy. If you get locked out, log in through the browser console in your VPS provider's control panel and run sudo nft flush ruleset to remove all rules.
Tables, chains and rules
- A table is a container. Its family decides what traffic it sees:
ip(IPv4),ip6(IPv6), orinet(both). Useinetfor a server firewall. - A chain holds rules. A base chain is attached to a kernel hook such as
input,forwardoroutput, and has a policy (acceptordrop) for packets that match no rule. - A rule has match expressions and a verdict, for example
tcp dport 22 accept. Rules are checked top to bottom and the first verdict wins.
See what is loaded right now:
sudo nft list rulesetAn empty output means no rules at all. If you see tables named firewalld, ufw or docker, another tool is managing the firewall.
Try commands interactively
You can build rules one command at a time. These are live immediately but not saved:
sudo nft add table inet demo
sudo nft add chain inet demo input '{ type filter hook input priority 0; policy accept; }'
sudo nft add rule inet demo input tcp dport 8080 counter accept
sudo nft -a list table inet demoThe -a flag shows each rule's handle, which you need to delete it:
sudo nft delete rule inet demo input handle 3
sudo nft delete table inet demoThe policy here is accept, so experimenting cannot lock you out. For a real firewall, a config file is easier to read and review.
Write the ruleset file
Create the file. On Ubuntu and Debian it is /etc/nftables.conf. On AlmaLinux and Rocky Linux, the service loads /etc/sysconfig/nftables.conf, which can include files from /etc/nftables/. Put the same content in that file instead.
#!/usr/sbin/nft -f
flush ruleset
table inet filter {
chain input {
type filter hook input priority filter; policy drop;
ct state established,related accept
ct state invalid drop
iif "lo" accept
meta l4proto { icmp, ipv6-icmp } accept
tcp dport 22 accept comment "SSH"
tcp dport { 80, 443 } accept comment "Web"
}
chain forward {
type filter hook forward priority filter; policy drop;
}
chain output {
type filter hook output priority filter; policy accept;
}
}What each part does:
flush rulesetclears old rules so loading the file twice does not stack duplicates. It also removes rules created by other tools, such as Docker.established,relatedlets replies to your own connections back in. Without it, evenapt updatebreaks.lois the loopback interface. Local services talk to each other through it.- ICMP and ICMPv6 keep ping, path MTU discovery and IPv6 neighbour discovery working. IPv6 does not work properly without ICMPv6.
- Change
22if your SSH runs on another port. forwarddrops routed traffic. Docker, VPNs and NAT gateways need forwarding rules, so adjust this chain if you run them.
Test and load it
Check the syntax without applying anything:
sudo nft -c -f /etc/nftables.confNo output means the file is valid. For extra safety, schedule an automatic flush in case the new rules cut you off, as described in Test firewall changes with an automatic rollback:
sudo systemd-run --on-active=5m --unit=nft-rollback /usr/sbin/nft flush rulesetNow load the ruleset:
sudo nft -f /etc/nftables.confOpen a new SSH session. If it connects, cancel the rollback:
sudo systemctl stop nft-rollback.timerLoad it at boot
Enable the nftables service so the file is applied on every boot:
sudo systemctl enable --now nftablesOn AlmaLinux and Rocky Linux, make sure firewalld is disabled first, or the two will conflict.
Check it works
sudo nft list ruleset
systemctl is-enabled nftablesFrom another computer, test an allowed and a blocked port:
nc -vz 203.0.113.10 22
nc -vz -w 3 203.0.113.10 3306Replace 203.0.113.10 with your server's IP. Port 22 should connect. Port 3306 should time out, even if MySQL is running.
Troubleshooting
- "Could not process rule: No such file or directory": you referenced a table or chain that does not exist yet. Create it first, or use the file method.
- Updates and DNS fail after loading: the
established,relatedrule is missing or placed after a drop. Keep it near the top. - Rules gone after reboot: the nftables service is not enabled, or you edited the wrong file for your distro.
- Docker containers lost network:
flush rulesetand the forward drop policy removed Docker's rules. Restart Docker after loading, and allow forwarding for its bridges. - Locked out: use the browser console, run
sudo nft flush ruleset, fix the SSH port in the file and reload.
Next steps
- Add fast allowlists and blocklists: Use nftables sets for fast allowlists and blocklists.
- Coming from iptables? Migrate iptables rules to nftables.
