Physical Address
304 North Cardinal St.
Dorchester Center, MA 02124
Physical Address
304 North Cardinal St.
Dorchester Center, MA 02124

If you have been relying on UFW for years, it has probably served you well for basic scenarios. But the moment you need NAT, port forwarding, rate limiting with custom thresholds, or complex chain logic, you quickly hit a wall that UFW simply cannot break through.
That is exactly where nftables steps in. It is not just a newer version of iptables — it is an entirely different approach to packet filtering, built from the ground up to be faster, cleaner, and far more flexible. On Ubuntu 26.04, nftables is the native firewall framework, and UFW itself sits on top of it behind the scenes.

This guide walks you through everything: the architecture of nftables, how to build tables, chains, and rules from scratch, how to configure NAT and port forwarding, and how to make your ruleset survive a reboot. A hands-on lab section at the end gives you reproducible commands to practice on your own Ubuntu 26.04 system.
For most of the 2000s and 2010s, iptables was the standard Linux firewall tool. It worked, but it carried significant baggage. Separate tools existed for IPv4, IPv6, ARP, and bridge traffic — iptables, ip6tables, arptables, ebtables — meaning any complete firewall setup required managing four separate rulesets simultaneously.
nftables collapses all of that into a single, unified framework. Using the inet address family, one set of rules handles both IPv4 and IPv6 traffic. Beyond unification, there are meaningful performance and usability improvements:
| ℹ️ Ubuntu 26.04 and iptables When you type iptables on Ubuntu 26.04, you are not talking to a real iptables kernel module. The command is a symbolic link to xtables-nft-multi — a compatibility shim that translates your iptables syntax into equivalent nftables rules. Everything runs through nftables under the hood. This is why learning nftables directly is worth your time. |
Before writing a single rule, it helps to understand the three-level hierarchy that nftables uses to organize all firewall logic. Every piece of configuration fits into one of these three layers.
| Concept | Description |
| Tables | Top-level containers. You choose a name and an address family (ip, ip6, inet, arp, bridge, netdev). Tables do not filter anything on their own. |
| Chains | Live inside tables. Base chains hook into specific points in the kernel’s packet-processing pipeline (input, output, forward, prerouting, postrouting). Regular chains are called from rules and act like subroutines. |
| Rules | The actual filtering logic. Each rule contains match criteria (protocol, port, IP address, connection state) and a verdict (accept, drop, reject, log, jump to another chain). |
| Families | inet handles both IPv4 and IPv6 — use this for nearly all server firewall work. ip is IPv4-only. netdev hooks in at the network device level, before any routing decision. |
| Hooks | input: traffic destined for this machine. output: traffic leaving this machine. forward: traffic routed through this machine. prerouting / postrouting: used for NAT. |
| Priority | Determines chain execution order when multiple chains hook into the same point. Lower values run first. The filter keyword maps to priority 0 as a readable alias. |
| Policy | The default verdict when a packet exits a chain without matching any rule. accept lets it through; drop silently discards it. |
NAT and port forwarding built in: No separate kernel modules required. Add a nat table with prerouting and postrouting chains and you are done.
Ubuntu 26.04 includes nftables by default. Confirm it is installed and start the systemd service.
# Confirm nftables is installed
sudo apt update && sudo apt install -y nftables
# Enable the service to start at boot and start it now
sudo systemctl enable --now nftables
# Verify the service is active
sudo systemctl status nftables
# Check the nft version
nft --version
# List the current (empty) ruleset
sudo nft list ruleset
| ℹ️ Disable UFW first If UFW is active, disable it before proceeding: sudo ufw disable Running both simultaneously will cause rule conflicts and unexpected behavior. |
nftables has no default tables. You must create them. Start with an inet table (covers both IPv4 and IPv6) named ‘firewall’:
# Create the main inet table
sudo nft add table inet firewall
# Create the input base chain with a default DROP policy
sudo nft add chain inet firewall input '{ type filter hook input priority filter; policy drop; }'
# Create the output chain (accept all outbound by default)
sudo nft add chain inet firewall output '{ type filter hook output priority filter; policy accept; }'
# Create the forward chain (drop by default for a server; change to accept for a router)
sudo nft add chain inet firewall forward '{ type filter hook forward priority filter; policy drop; }'
# Verify the table and chains exist
sudo nft list table inet firewall
Connection tracking is the foundation of a modern stateful firewall. These three rules handle the bulk of legitimate traffic with minimal rule overhead:
# Allow all traffic on the loopback interface (essential for local services)
sudo nft add rule inet firewall input iif lo accept
# Allow established and related connections (return traffic for outbound connections)
sudo nft add rule inet firewall input ct state established,related accept
# Drop invalid packets (malformed, untracked, or spoofed state)
sudo nft add rule inet firewall input ct state invalid drop
# Verify the rules so far
sudo nft list chain inet firewall input
Without this rule, your server cannot receive responses to connections it initiates.
DNS queries, apt package downloads, curl requests — all would silently fail.
This rule is essential in any default-drop input policy.
Now allow the specific services you need. Always add SSH first — losing SSH access to a remote server mid-configuration is a common and painful mistake
# Allow incoming SSH (port 22) - ADD THIS BEFORE ANYTHING ELSE on a remote server
sudo nft add rule inet firewall input tcp dport 22 accept
# Allow HTTP and HTTPS (ports 80 and 443) using a set for efficiency
sudo nft add rule inet firewall input tcp dport '{ 80, 443 }' accept
# Allow ICMP (ping) for IPv4
sudo nft add rule inet firewall input ip protocol icmp accept
# Allow ICMPv6 for IPv6 neighbor discovery (required for IPv6 to work)
sudo nft add rule inet firewall input ip6 nexthdr icmpv6 accept
# Log packets that will be dropped (useful for debugging - optional)
sudo nft add rule inet firewall input log prefix \"nft-drop: \" level warn
# List the complete input chain
sudo nft list chain inet firewall input
To allow an internal subnet (e.g., 10.0.0.0/24) to access the internet through your Ubuntu machine, configure NAT masquerade. This is common in home labs and gateway setups:
# Enable IP forwarding in the kernel (required for NAT/routing)
echo 'net.ipv4.ip_forward = 1' | sudo tee -a /etc/sysctl.conf
sudo sysctl -p
# Create a separate nat table
sudo nft add table inet nat
# Create the postrouting chain (handles packets leaving a network interface)
sudo nft add chain inet nat postrouting '{ type nat hook postrouting priority srcnat; }'
# Add masquerade rule for traffic from the internal subnet (replace eth0 with your WAN interface)
sudo nft add rule inet nat postrouting ip saddr 10.0.0.0/24 oif eth0 masquerade
# Also update the forward chain to accept traffic from the internal subnet
sudo nft add rule inet firewall forward iif eth1 oif eth0 ct state new,established,related accept
sudo nft add rule inet firewall forward iif eth0 oif eth1 ct state established,related accept
# Verify the nat table
sudo nft list table inet nat
Port forwarding redirects traffic arriving on one port to a different destination port or IP. This is useful for exposing an internal service on a non-standard public port:
# Add the prerouting chain to the nat table
sudo nft add chain inet nat prerouting '{ type nat hook prerouting priority dstnat; }'
# Forward external port 8080 to internal port 80 on the same machine
sudo nft add rule inet nat prerouting tcp dport 8080 redirect to :80
# Forward external port 2222 to an internal server at 192.168.1.50:22
# (replace eth0 with your public-facing interface)
sudo nft add rule inet nat prerouting iif eth0 tcp dport 2222 dnat to 192.168.1.50:22
# Verify the prerouting chain
sudo nft list chain inet nat prerouting
All rules added with nft commands are lost on reboot. The nftables systemd service loads /etc/nftables.conf at startup. Save your current ruleset there
# Back up the existing nftables.conf
sudo cp /etc/nftables.conf /etc/nftables.conf.bak
# Write the current in-memory ruleset to the config file
sudo nft list ruleset | sudo tee /etc/nftables.conf
# Test that the config file is syntactically valid (dry run)
sudo nft -c -f /etc/nftables.conf
# Reload the nftables service to apply the saved config
sudo systemctl reload nftables
# Verify the service loaded the config successfully
sudo systemctl status nftables
# Example /etc/nftables.conf structure after saving
#!/usr/sbin/nft -f
flush ruleset
table inet firewall {
chain input {
type filter hook input priority filter; policy drop;
iif lo accept
ct state established,related accept
ct state invalid drop
tcp dport 22 accept
tcp dport { 80, 443 } accept
ip protocol icmp accept
ip6 nexthdr icmpv6 accept
log prefix \"nft-drop: \" level warn
}
chain output { type filter hook output priority filter; policy accept; }
chain forward { type filter hook forward priority filter; policy drop; }
}
table inet nat {
chain prerouting { type nat hook prerouting priority dstnat; }
chain postrouting { type nat hook postrouting priority srcnat; }
}
# === nftables Verification Commands ===
# List complete ruleset with rule handles (useful for deleting specific rules)
sudo nft -a list ruleset
# List only the input chain
sudo nft list chain inet firewall input
# Show packet and byte counters for rules
sudo nft list ruleset -a
# Monitor rule matches in real time (Ctrl+C to stop)
sudo nft monitor
# Test SSH connectivity from another terminal before closing this one
ssh user@YOUR_SERVER_IP
# Test HTTP response
curl -I http://localhost
# Check nftables service logs
sudo journalctl -u nftables -n 50
# See what the log rule is catching sudo journalctl -k | grep ‘nft-drop’
sudo journalctl -k | grep ‘nft-drop’
nftables is not just the future of Linux firewalling — on Ubuntu 26.04, it is the present. Every packet that enters, exits, or passes through your system is processed by nftables, even if you have never typed the word nft in your life.
Learning to work with it directly unlocks capabilities that UFW simply cannot offer: custom chain hierarchies, atomic ruleset swaps, native sets for multi-value matching, and full NAT and port-forwarding support. The syntax is cleaner than iptables, the performance is better, and the persistence model via /etc/nftables.conf is straightforward.
The lab tutorial below walks you through every step from installation through a production-ready ruleset with SSH protection, web traffic filtering, NAT masquerade, and port forwarding — all on Ubuntu 26.04, all verified and repeatable.
