nftables Firewall Mastery: The complete Configuration for Ubuntu 26.04

Introduction

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.

Why nftables? The Case Against iptables

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:

  • Atomic rule replacement: All rule changes are applied as a single atomic transaction. No half-applied states, no brief window of unprotected traffic.
  • Built-in sets and maps: Instead of calling out to ipset as a separate tool, nftables has native set and map data structures for high-performance multi-value matching.
  • No default tables or chains: iptables forced predefined chains on every packet flowing through the system, even if you had no rules. nftables only processes what you explicitly create.
  • Multiple actions per rule: A single nftables rule can perform logging, counting, and accepting in one line. iptables required separate rules for each action.
  • Cleaner, human-readable syntax: The nft command language is declarative and consistent across all address families.
ℹ️  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.

nftables Architecture: Tables, Chains, and Rules

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.

ConceptDescription
TablesTop-level containers. You choose a name and an address family (ip, ip6, inet, arp, bridge, netdev). Tables do not filter anything on their own.
ChainsLive 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.
RulesThe 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).
Familiesinet 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.
Hooksinput: traffic destined for this machine. output: traffic leaving this machine. forward: traffic routed through this machine. prerouting / postrouting: used for NAT.
PriorityDetermines 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.
PolicyThe default verdict when a packet exits a chain without matching any rule. accept lets it through; drop silently discards it.

Key Highlights

  • Single tool, all protocols: nftables with the inet family replaces iptables, ip6tables, arptables, and ebtables in one clean interface.
  • Native to Ubuntu 26.04: No installation required for modern Ubuntu — the nft utility and kernel subsystem are present by default.
  • UFW conflict warning: UFW manages nftables rules on your behalf. Running both simultaneously causes conflicts. Disable UFW before configuring nftables directly.
  • Stateful filtering via connection tracking: The ct state keyword lets rules accept established/related traffic and drop invalid packets with minimal rule count.
  • Persistence via /etc/nftables.conf: The nftables systemd service loads this file at boot. Save your ruleset here to survive reboots.
  • Atomic updates: Apply an entire ruleset from a file with nft -f, which commits all changes at once — no partial states.

NAT and port forwarding built in: No separate kernel modules required. Add a nat table with prerouting and postrouting chains and you are done.

Configuring nftables on Ubuntu 26.04

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.

Create a Base Filter Table and Input Chain

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

Add Stateful Connection Tracking Rules

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

Why ct state established,related matters

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.

Allow SSH, HTTP, and HTTPS Traffic

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

Configure NAT — Masquerade for Outbound Traffic

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

Configure Port Forwarding

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

Persist the Ruleset — Save to /etc/nftables.conf

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; }
}

Verify, Test, and Monitor

# === 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’

Conclusion

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.

Leave a Reply

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