If iptables -L -n has been your muscle memory for the past decade, upgrading to CentOS Stream 9 brings a major change. Starting with this version, nftables officially replaces the aging iptables as the default firewall management tool.
This article draws from my hands-on experience migrating systems from CentOS 7 to the RHEL 9 family. You will see that nftables isn’t intimidating; in fact, it is much more logical and cleaner than the old way.
Why you should switch from iptables right now?
The biggest nightmare when managing legacy server clusters is maintaining iptables (IPv4), ip6tables (IPv6), arptables, and ebtables simultaneously. Each tool has its own syntax despite having similar purposes.
nftables solves this fragmentation with a unified framework. Key improvements include:
- Dual-stack support: A single command handles both IPv4 and IPv6 via the
inetfamily. - Superior speed: Instead of scanning line by line (O(n)), nftables uses Sets data structures for constant-time lookups (O(1)). With a blocklist of 10,000 IPs, your CPU will barely feel the difference.
- Flexible structure: You define your own Tables and Chains based on your needs, no longer restricted to rigid default tables.
Core Architecture: Table, Chain, and Rule
Before diving into commands, visualize the nftables hierarchy. Unlike iptables, nftables starts with a “blank slate”:
- Tables: The top-level container for Chains. You must choose a “family” (such as ip, ip6, inet) upon creation.
- Chains: Where Rules are stored. Chains are attached to kernel “hooks” like input, output, or forward.
- Rules: Command lines that decide the fate of packets (accept, drop, reject).
Real-world Deployment on CentOS Stream 9
Typically, nftables is pre-installed on CentOS Stream 9. If you are using the Minimal version, install it via dnf.
# Install and enable the service
sudo dnf install nftables -y
sudo systemctl enable --now nftables
Note: Disable firewalld to avoid configuration conflicts. Running two firewall services simultaneously often leads to networking issues that are very difficult to debug.
sudo systemctl stop firewalld
sudo systemctl disable firewalld
1. Flushing the configuration
To ensure there are no redundant rules, clear all existing settings:
sudo nft flush ruleset
2. Initializing Tables and Base Chains
We will create a table named my_filter using the inet family. Then, set up basic chains for incoming, outgoing, and forwarded traffic.
# Create table
sudo nft add table inet my_filter
# Create INPUT chain (default drop for security)
sudo nft add chain inet my_filter input { type filter hook input priority 0 \; policy drop \; }
# Create FORWARD and OUTPUT chains
sudo nft add chain inet my_filter forward { type filter hook forward priority 0 \; policy drop \; }
sudo nft add chain inet my_filter output { type filter hook output priority 0 \; policy accept \; }
3. Setting up Essential Rules (SSH, Loopback)
Don’t forget this step, or you will be locked out of your server immediately due to the policy drop above.
# Allow loopback traffic
sudo nft add rule inet my_filter input iif lo accept
# Maintain active connections (established/related)
sudo nft add rule inet my_filter input ct state established,related accept
# Open SSH port (22)
sudo nft add rule inet my_filter input tcp dport 22 accept
Advanced Features: Sets and Verdict Maps
These are the secret weapons that allow nftables to surpass iptables in both performance and code elegance.
Blocking Bulk IPs with Sets
Instead of creating 100 rules to block 100 IPs, you can group them into a single Set.
# Create a blocklist
sudo nft add set inet my_filter blackhole { type ipv4_addr \; }
# Add offending IPs
sudo nft add element inet my_filter blackhole { 192.168.1.100, 10.0.0.5, 172.16.0.10 }
# A single rule to block the entire list
sudo nft insert rule inet my_filter input ip saddr @blackhole drop
Traffic Steering with Verdict Maps
Verdict Maps allow processing multiple conditions in a single command. For example: allow ports 80 and 443, but drop port 23 (Telnet) immediately.
sudo nft add rule inet my_filter input tcp dport vmap { 80 : accept, 443 : accept, 23 : drop }
This syntax makes your configuration file significantly more concise and readable.
Persistent Configuration
Every nft command you entered resides only in RAM. If the server reboots, the configuration will be lost. Save them to a system file to load automatically on boot.
# Export configuration to the system definition file
sudo nft list ruleset | sudo tee /etc/sysconfig/nftables.conf
# Restart to verify
sudo systemctl restart nftables
My advice: Always back up your nftables.conf file before making major changes. If a misconfiguration causes a network loss, a datacenter technician can restore the old version in seconds.
Monitoring and Debugging
To check active rules with their identifiers (handles), use:
sudo nft list ruleset -a
Handles are extremely useful when you want to delete a specific rule without retyping the entire lengthy command:
# Delete the rule with handle number 5 in the input chain
sudo nft delete rule inet my_filter input handle 5
Conclusion
Switching to nftables on CentOS Stream 9 might feel a bit unfamiliar at first due to the syntax. However, once you master the Table and Set mindset, you will possess a powerful, streamlined, and high-performance firewall system.
If your organization is planning a migration to RHEL 9 or equivalent distributions, don’t hesitate to adopt nftables. This isn’t just a technological shift; it’s the new standard for optimizing modern Linux system administration.

