In 2026 there are four firewall tools you can find in production on Linux: iptables, the legacy interface that everyone still has muscle memory for; nftables, the modern kernel-side replacement that has been default since 2018-2020 depending on the distro; firewalld, the dynamic zone-based daemon used on the RHEL family; and ufw, the human-readable Debian/Ubuntu front-end. All four ultimately program the same kernel — they are user-facing wrappers around the netfilter or nf_tables packet filter. This article compares the four, shows the same rule recipe expressed in each syntax, explains migration patterns, and ends with a decision tree for new installations.
Contents
- Why four firewall tools in 2026
- The kernel underneath: netfilter vs nf_tables
- iptables: the legacy interface
- nftables: the modern kernel ABI
- firewalld: the dynamic state daemon
- ufw: the human-readable front-end
- Per-distro defaults
- Recipe: same rule in all four syntaxes
- Performance and feature comparison
- Migration patterns
- Decision tree for new installations
- Common gotchas
- FAQ
Why four firewall tools in 2026
The history is the explanation. iptables shipped with Linux 2.4 in 2001 as the user-facing tool for the netfilter framework. It became the universal default for two decades, accumulated thousands of tutorials, runbooks, Ansible roles and Stack Overflow answers, and entered into the muscle memory of every Linux admin who started before 2018. nftables was developed by the netfilter project itself starting in 2014 as the official successor, addressing iptables’ performance and expressiveness limitations. The Linux kernel quietly switched to the nf_tables backend in 4.x, and distros started shipping nftables as the default around 2018-2020 (Debian 10, RHEL 8, Ubuntu 20.04, openSUSE Tumbleweed). The iptables command still exists on every modern distro but on most of them, it is the iptables-nft shim that translates iptables syntax into nftables rules under the hood. So when you type iptables -L on Rocky 9, you are seeing nftables rules being rendered as iptables would have shown them.
firewalld and ufw are higher-level abstractions that came later for different audiences. firewalld (2011, Red Hat) wraps the kernel layer behind a daemon that exposes zones, services, runtime versus permanent rules, and a D-Bus API. It is the only one of the four with a state daemon. ufw (2008, Ubuntu) does the opposite: it is a thin shell-script wrapper over iptables that turns common operations into one-line commands. Both target users who want a firewall without writing chains.
The kernel underneath: netfilter vs nf_tables
All four tools eventually program a single kernel structure: the packet filter. There are two generations of that structure in the kernel:
- x_tables (the netfilter framework backing iptables, ip6tables, ebtables and arptables). One module per protocol family, fixed table structure, limited expressiveness for compound rules.
- nf_tables (the unified replacement). One framework for v4/v6/bridge/arp, programmable VM bytecode in the kernel, atomic rule replacement, much better performance under high rule counts.
Modern distros run the kernel’s nf_tables backend exclusively. iptables, iptables-legacy, iptables-nft, firewall-cmd, ufw, nft are all user-space tools that end up writing nf_tables rules. The exception is when iptables-legacy is explicitly installed alongside the nft backend — that programs the old x_tables structure, which still exists but is deprecated.
iptables: the legacy interface
iptables organises rules into tables (filter, nat, mangle, raw) and chains within each table (INPUT, OUTPUT, FORWARD for the filter table, plus custom chains). Each rule matches packet attributes and decides an action (ACCEPT, DROP, REJECT, jump to another chain).
# List current rules sudo iptables -L -n -v --line-numbers # Allow SSH from anywhere sudo iptables -A INPUT -p tcp --dport 22 -j ACCEPT # Set default policy to drop sudo iptables -P INPUT DROP # Save rules persistently (Debian/Ubuntu via iptables-persistent package) sudo netfilter-persistent save # RHEL 7 style sudo service iptables save
iptables’ main strengths are familiarity and the immense body of existing documentation. Its weaknesses are: rules cannot be replaced atomically (every -I or -D mutates the live rule set), syntax for any compound condition is verbose, and performance under rule counts above a few thousand degrades non-linearly.
nftables: the modern kernel ABI
nftables replaces the table-and-chain model with a more flexible scheme: you define tables (any name, scoped to a protocol family), chains inside tables (with explicit hook attachment), rules inside chains, and sets and maps as first-class objects for grouping addresses, ports, or interfaces.
# /etc/nftables.conf — full example
#!/usr/sbin/nft -f
flush ruleset
table inet filter {
set allowed_tcp {
type inet_service
elements = { 22, 80, 443 }
}
chain input {
type filter hook input priority 0;
policy drop;
iif lo accept
ct state established,related accept
tcp dport @allowed_tcp accept
ip protocol icmp accept
ip6 nexthdr icmpv6 accept
}
}
# Apply
sudo nft -f /etc/nftables.conf
# List
sudo nft list ruleset
nftables’ advantages over iptables: atomic rule replacement (the entire ruleset swaps in one operation), set and map types that make “allow 17 ports” a single line instead of 17 rules, native IPv4 + IPv6 handling in the same rule, and a kernel implementation that scales to tens of thousands of rules without measurable performance degradation. The downside is the syntax is new — runbooks and tutorials are still catching up.
firewalld: the dynamic state daemon
firewalld introduces three concepts that the other tools do not have: zones (a connection or interface belongs to a zone, the zone has a default policy), services (named bundles of port + protocol — “ssh” rather than “tcp/22”), and runtime versus permanent (changes made without --permanent apply immediately but disappear on reload or reboot).
# Inspect current state sudo firewall-cmd --get-default-zone sudo firewall-cmd --list-all # Allow SSH, HTTP, HTTPS persistently sudo firewall-cmd --zone=public --add-service=ssh --permanent sudo firewall-cmd --zone=public --add-service=http --permanent sudo firewall-cmd --zone=public --add-service=https --permanent # Custom port range sudo firewall-cmd --zone=public --add-port=8080-8090/tcp --permanent # Apply sudo firewall-cmd --reload
firewalld’s strengths are dynamic operations (the daemon allows changes without flushing the whole ruleset, which iptables does on every save), zone-based logic that suits roaming laptops and multi-interface servers, and a D-Bus API that GUI tools can integrate with. Weaknesses: the abstraction layer hides what is actually being programmed, debugging requires understanding both firewalld zones and the underlying nft ruleset, and the daemon is one more moving part on minimal server installs.
ufw: the human-readable front-end
ufw is the shortest path from “I want SSH and HTTPS open” to “done”:
sudo ufw default deny incoming sudo ufw default allow outgoing sudo ufw allow 22/tcp comment 'SSH' sudo ufw allow 80/tcp comment 'HTTP' sudo ufw allow 443/tcp comment 'HTTPS' sudo ufw enable # Verify sudo ufw status verbose # Rate-limit (built-in) sudo ufw limit 22/tcp
ufw is opinionated: it organises rules so most users do not have to think about chains, NAT, or table jumps. The strengths are the smallest learning curve and the fastest “open this port persistently” workflow. The weaknesses are: limited expressiveness (anything beyond simple allow/deny per port falls outside ufw’s vocabulary and you reach for raw iptables or nft), and it does not coexist cleanly with firewalld on the same host (pick one).
Per-distro defaults
Which tool ships pre-installed on each distro covered by our Distro Reference:
| Distro | Default firewall front-end | Kernel backend |
|---|---|---|
| Ubuntu 24.04 LTS | ufw (not enabled by default) | nf_tables |
| Debian 12 Bookworm | None pre-installed (nftables.service available) | nf_tables |
| Fedora 40 | firewalld (enabled) | nf_tables |
| Rocky 9 / Alma 9 / RHEL 9 | firewalld (enabled) | nf_tables |
| Arch Linux | None pre-installed | nf_tables (available via nftables package) |
| openSUSE Leap 15.5 | firewalld (enabled, since Leap 15.0) | nf_tables |
| Alpine Linux 3.19 | None pre-installed | nf_tables (via nftables package) |
Two non-obvious points: Ubuntu ships ufw but does not enable it — a fresh server install has no firewall active until you run ufw enable. Arch and Alpine ship no firewall at all, which catches many first-time admins: a freshly provisioned host has every port open until you install and enable one.
Recipe: same rule in all four syntaxes
The shared baseline: deny everything inbound except SSH (22), HTTP (80), HTTPS (443) and established connections.
iptables
sudo iptables -P INPUT DROP sudo iptables -A INPUT -i lo -j ACCEPT sudo iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT sudo iptables -A INPUT -p tcp --dport 22 -j ACCEPT sudo iptables -A INPUT -p tcp --dport 80 -j ACCEPT sudo iptables -A INPUT -p tcp --dport 443 -j ACCEPT sudo iptables -A INPUT -p icmp -j ACCEPT sudo netfilter-persistent save
nftables
cat | sudo tee /etc/nftables.conf <<'EOF'
#!/usr/sbin/nft -f
flush ruleset
table inet filter {
chain input {
type filter hook input priority 0;
policy drop;
iif lo accept
ct state established,related accept
tcp dport { 22, 80, 443 } accept
ip protocol icmp accept
ip6 nexthdr icmpv6 accept
}
}
EOF
sudo systemctl enable --now nftables
sudo nft -f /etc/nftables.conf
firewalld
sudo firewall-cmd --zone=public --set-target=DROP --permanent sudo firewall-cmd --zone=public --add-service=ssh --permanent sudo firewall-cmd --zone=public --add-service=http --permanent sudo firewall-cmd --zone=public --add-service=https --permanent sudo firewall-cmd --reload
ufw
sudo ufw default deny incoming sudo ufw default allow outgoing sudo ufw allow 22/tcp sudo ufw allow 80/tcp sudo ufw allow 443/tcp sudo ufw enable
Same result, four very different lines of code. The nft version is the most compact thanks to the { 22, 80, 443 } set syntax. The firewalld version reads almost like English. The ufw version is the fastest to type. The iptables version is the most explicit about what is happening.
Performance and feature comparison
| Feature | iptables | nftables | firewalld | ufw |
|---|---|---|---|---|
| Atomic rule replacement | No | Yes | Yes (via daemon) | No (uses iptables under hood) |
| Sets and maps | No | Yes | Limited (ipset) | No |
| IPv4 + IPv6 in one rule | No (two tools) | Yes (inet family) | Yes (transparent) | Yes (parallel rules) |
| Dynamic API (state daemon) | No | No (rules are static config) | Yes (D-Bus) | No |
| Zones / location awareness | No | No | Yes | No |
| Performance at 10k+ rules | Slow (linear) | Fast | Depends on backend (now nft) | Inherits from iptables |
| Learning curve | Steep (deep) | Medium | Medium (concepts) | Lowest |
| Ecosystem (docs, runbooks) | Huge | Growing | Solid in RHEL world | Solid in Debian/Ubuntu |
Migration patterns
iptables → nftables
The official conversion tool is iptables-restore-translate. It reads an iptables-save output and emits the equivalent nftables ruleset. Use it as a starting point, then clean up by hand to take advantage of sets and maps:
sudo iptables-save > /tmp/rules-v4.txt sudo ip6tables-save > /tmp/rules-v6.txt iptables-restore-translate -f /tmp/rules-v4.txt > /tmp/rules.nft ip6tables-restore-translate -f /tmp/rules-v6.txt >> /tmp/rules.nft # Review the output, edit as needed, then load sudo nft -f /tmp/rules.nft sudo systemctl disable --now iptables # remove the old service sudo systemctl enable --now nftables
ufw → firewalld (or vice versa)
The two cannot coexist; the migration is “disable one, install the other, recreate the rules”. There is no syntax-level conversion tool because the abstractions are so different (port-level rules vs zone-based rules). Manually re-implement using the cheatsheet in the recipe section above.
Legacy iptables-save format → nftables permanent config on Debian/Ubuntu
If you have an existing /etc/iptables/rules.v4 from iptables-persistent, the cleanest migration on Debian 12 / Ubuntu 24.04 is:
sudo apt install nftables iptables-nftables-compat sudo iptables-restore-translate -f /etc/iptables/rules.v4 > /etc/nftables.conf sudo systemctl enable --now nftables sudo systemctl disable netfilter-persistent
Decision tree for new installations
- Single Debian / Ubuntu server, a handful of ports, ops team prefers cheatsheets → ufw. Lowest cognitive cost, fastest “open port” workflow.
- RHEL family server (Rocky, Alma, RHEL, Fedora) → firewalld. It is the default; fighting the default is rarely worth the cost.
- Container host, K8s node, anything where rules change frequently and atomically → nftables directly. Atomic replacement, performance at scale.
- Roaming laptop with VPN, captive portals, multiple location profiles → firewalld with zones. It is built for exactly this.
- Fleet management, runbook-heavy environment, infrastructure as code → nftables (declarative
nftables.confrenders cleanly from templates). - Legacy environment with hundreds of existing iptables rules → translate via
iptables-restore-translate, then maintain as nftables going forward.
See the exact command for your distro?
Our Linux Distro Reference shows the firewall syntax, file paths, workflow and gotcha for each of the 7 supported distros in one click.
Common gotchas
- Default policy of DROP without an allow rule on the loopback: locks out the host from itself. Always
-A INPUT -i lo -j ACCEPT(iptables) oriif lo accept(nft) before changing the default policy. - Forgetting the established/related rule: outbound connections work but replies do not get through. Always allow
state established,relatedon the input chain. - ufw and Docker conflict: Docker writes its own iptables/nft rules that bypass ufw. Use the
ufw-dockerhelper or configure Docker with--iptables=falseif you need ufw to control container traffic. - firewalld silent without –permanent: rules added without
--permanentdisappear after a reload or reboot, with no warning. Get in the habit of always using--permanent+--reload. - Multiple firewall tools on one host: ufw + firewalld + raw iptables fighting. Pick one, disable the others (
sudo systemctl mask). - IPv6 forgotten: a rule that allows TCP/22 on v4 may leave SSH wide open on v6 if you did not also write the v6 rule. nftables’
inetfamily solves this; iptables requires bothiptablesandip6tables. - Persistence not configured: rules applied via
iptables -Aare gone after reboot unless you save them. Distros each have their own save mechanism (iptables-persistent,nftables.service,firewall-cmd --reload).
FAQ
Should I learn nftables in 2026?
Yes. The kernel has moved on; nftables is what runs underneath everything. You can keep using iptables-nft for legacy rules but knowing native nft syntax pays off when you read modern docs, debug rule loading, or use sets and maps for scale.
Is iptables going away?
The iptables command will be around for years because of the install base. The underlying x_tables backend is technically deprecated but still maintained for compatibility. The realistic prediction: iptables-nft stays usable through 2030, iptables-legacy gets dropped from most distros by 2028.
Can I use ufw on RHEL or firewalld on Ubuntu?
Technically yes. ufw is in EPEL for RHEL; firewalld is in Ubuntu’s universe repository. Neither is the path of least resistance. The cost is using a tool the rest of the ecosystem (documentation, package interactions, your colleagues’ muscle memory) does not expect.
What about pf?
pf is BSD-only (OpenBSD, FreeBSD, macOS). It does not exist on Linux. If you are coming from BSD and reflexively typing pfctl, the closest mental model on Linux is nftables — declarative, table-and-chain, atomic replacement.
Do I still need fail2ban with a proper firewall?
Yes. The firewall blocks based on port and source IP — it does not look at the content of established connections. fail2ban (or its successors) watch the application logs and inject temporary block rules when a pattern of failures matches. They complement, not replace.
Can I run a stateless firewall for performance?
nftables supports stateless rules (skip the ct state match) and can run at line rate on modern NICs for simple drop/allow logic. But stateless rules cannot detect reply traffic — every direction needs an explicit rule. Useful for edge routers, overkill for application servers.













