NFTables is the modern replacement for iptables on Linux, and it’s the default firewall on recent Ubuntu and Debian releases. It’s not just a collection of commands; it’s a framework that allows for more flexible and efficient packet filtering.
Let’s set up a basic NFTables firewall on Ubuntu/Debian.
First, ensure you have NFTables installed. It’s usually present by default.
sudo apt update
sudo apt install nftables
After installation, the NFTables service should be running. You can check its status:
sudo systemctl status nftables
If it’s not running, start and enable it:
sudo systemctl start nftables
sudo systemctl enable nftables
NFTables uses a configuration file, typically located at /etc/nftables.conf. This file defines tables, chains, and rules that dictate how network traffic is handled.
Let’s create a basic configuration to allow SSH and HTTP/HTTPS traffic while dropping everything else.
Here’s a sample /etc/nftables.conf:
#!/usr/sbin/nft -f
flush ruleset
table ip filter {
chain input {
type filter hook input priority 0; policy drop;
# Allow established and related connections
ct state established,related accept
# Allow loopback interface
iif lo accept
# Allow SSH (TCP port 22)
tcp dport 22 accept
# Allow HTTP (TCP port 80)
tcp dport 80 accept
# Allow HTTPS (TCP port 443)
tcp dport 443 accept
# Log dropped packets (optional, for debugging)
# This rule should be placed before any explicit drop rules if you want to see what's being dropped.
# For a strict policy, it's often omitted or placed at the very end.
# log prefix "nftables-dropped: " counter
# Explicitly drop invalid packets (optional but good practice)
# ct state invalid drop
}
chain forward {
type filter hook forward priority 0; policy drop;
}
chain output {
type filter hook output priority 0; policy accept;
}
}
Let’s break this down.
flush ruleset: This command clears any existing NFTables rules before applying the new configuration. It’s crucial for ensuring a clean slate.
table ip filter { ... }: This defines a table named filter for the ip address family (IPv4). Tables are containers for chains.
chain input { ... }: This defines the input chain. This chain is hooked into the input packet path, meaning it processes packets destined for the local machine. priority 0 is the default for filtering. policy drop; sets the default action for this chain to drop any packet that doesn’t match an explicit accept rule.
ct state established,related accept: This is a fundamental rule for stateful firewalls. ct refers to connection tracking. It allows packets that are part of an already established connection or are related to one (like ICMP error messages for an established UDP connection). Without this, your outgoing connections would be immediately dropped because the return packets wouldn’t be considered "new" and thus wouldn’t match any accept rules.
iif lo accept: This allows all traffic on the loopback interface (lo), which is essential for local services to communicate with each other.
tcp dport 22 accept: This rule accepts incoming TCP traffic destined for port 22, which is the standard port for SSH.
tcp dport 80 accept: Accepts incoming TCP traffic for port 80 (HTTP).
tcp dport 443 accept: Accepts incoming TCP traffic for port 443 (HTTPS).
chain forward { ... }: This chain is used for packets that are being routed through the machine, not destined for it. policy drop; ensures the server doesn’t act as a router by default.
chain output { ... }: This chain processes packets originating from the local machine. policy accept; means that by default, all outgoing traffic is allowed. You can add specific rules here to restrict outgoing connections if needed.
To apply this configuration, save the content to /etc/nftables.conf and then run:
sudo nft -f /etc/nftables.conf
You can verify the rules are loaded with:
sudo nft list ruleset
This command will display the currently active NFTables configuration.
The most surprising thing about NFTables is how much its expression of packet filtering rules has evolved. While iptables used a linear, sequential processing model for chains, NFTables introduces sets and maps as first-class citizens. This allows for vastly more efficient rule evaluation, especially when dealing with large lists of IPs or ports, as the system can perform lookups rather than iterating through numerous individual rules. You can define a set of IPs and then have a single rule that matches any IP within that set, significantly reducing the number of comparisons the kernel needs to make.
Let’s see this in action with a set for allowed SSH IPs.
First, create a set:
table ip filter {
set allowed_ssh_ips {
type ipv4_addr
elements = { 192.168.1.10, 10.0.0.5 } # Example IPs
}
chain input {
type filter hook input priority 0; policy drop;
ct state established,related accept
iif lo accept
# Allow SSH only from IPs in the 'allowed_ssh_ips' set
ip saddr @allowed_ssh_ips tcp dport 22 accept
tcp dport 80 accept
tcp dport 443 accept
}
# ... rest of the table ...
}
To apply this change, you’d typically edit /etc/nftables.conf and then run sudo nft -f /etc/nftables.conf.
The power of sets and maps becomes apparent when you need to manage dynamic lists. For instance, you can add or remove IPs from the allowed_ssh_ips set without reloading the entire ruleset, using commands like:
sudo nft add element ip filter allowed_ssh_ips { 192.168.1.20 }
sudo nft delete element ip filter allowed_ssh_ips { 192.168.1.10 }
This dynamic management is a significant upgrade over the static nature of iptables rules.
One of the lesser-discussed but incredibly powerful features of NFTables is its anonymous set functionality. When you define a set inline within a rule, like tcp dport { 80, 443 } accept, NFTables internally creates an anonymous set for those ports. This is conceptually similar to defining a named set explicitly but is more concise for simple, localized collections of values. The kernel can still optimize these inline definitions for faster lookups, making even seemingly simple rules more efficient than their iptables counterparts.
With your basic firewall in place, the next step is often to consider more advanced features like rate limiting to prevent brute-force attacks or setting up NAT for outbound connections.