Iptables vs Firewalld
Linux firewall management is going through a generational transition. For over two decades, iptables has been the standard tool for controlling network traffic on Linux systems. But firewalld has emerged as the preferred solution on Red Hat-based distributions, bringing a different philosophy to packet filtering.
Iptables connects directly to the kernel's netfilter framework, giving you complete control over every aspect of packet filtering. It's powerful and flexible, but that power comes wrapped in complex syntax that takes time to master.
Firewalld sits at a higher level, organizing your firewall around zones and services rather than raw packet filtering rules. It manages iptables or nftables in the background while presenting a more structured interface for common security scenarios.
This guide examines both tools, showing you what makes each one useful and helping you understand which approach fits your infrastructure better.
What iptables brings to Linux
Iptables has been the standard Linux firewall tool since the early 2000s. It provides direct access to the kernel's netfilter packet filtering system, which means you can control exactly how your system handles network traffic.
The tool organizes rules into tables and chains. The most commonly used table is the filter table, which contains three chains: INPUT (for incoming packets), OUTPUT (for outgoing packets), and FORWARD (for packets being routed through the system). Each chain contains rules that match packets based on criteria you specify.
A basic iptables rule looks like this:
sudo iptables -A INPUT -p tcp --dport 22 -j ACCEPT
This appends a rule to the INPUT chain that accepts TCP packets destined for port 22 (SSH). The syntax is precise but dense - every flag matters, and the order of parameters can be significant.
Iptables handles both IPv4 and IPv6 traffic through separate commands: iptables for IPv4 and ip6tables for IPv6. This separation means you often need to write two versions of each rule to cover both protocols.
The tool excels at granular control. You can match packets based on source and destination addresses, ports, protocols, connection states, TCP flags, packet length, and dozens of other criteria. You can modify packet headers, redirect traffic, implement complex NAT configurations, and create sophisticated routing policies.
But this power comes with a learning curve. Iptables syntax is notoriously difficult to remember, especially for complex rules. The command structure is verbose and unforgiving - miss a flag or get the order wrong, and your rule either fails or does something unexpected.
With iptables' low-level approach established, you can see how firewalld takes a completely different path by adding structure and abstraction.
How firewalld organizes security
Firewalld appeared in the early 2010s as part of Red Hat's effort to modernize Linux firewall management. Instead of thinking about individual packet filtering rules, firewalld encourages you to think about network zones and services.
A zone represents a level of trust for network connections. The default zones include public (untrusted networks), home (trusted home network), work (corporate network), internal (private networks), dmz (public-facing services), and trusted (completely trusted networks).
Each network interface on your system gets assigned to a zone, and the zone determines what traffic is allowed. This maps better to how people actually think about network security - you trust your home network more than random public WiFi, so you apply different rules to each.
Services define what ports and protocols applications need. Instead of remembering that SSH uses port 22 and HTTP uses port 80, you reference services by name:
sudo firewall-cmd --zone=public --add-service=ssh
sudo firewall-cmd --zone=public --add-service=http
Firewalld includes service definitions for hundreds of common applications. Each service is defined in an XML file that specifies all the ports and protocols that application needs.
The tool separates runtime and permanent configurations. Changes you make without the --permanent flag take effect immediately but don't survive reboots. This lets you test changes safely before committing them.
Firewalld manages iptables rules automatically. Every zone, service, and rule you configure in firewalld gets translated into iptables rules that netfilter actually uses. On newer systems, firewalld can use nftables instead as its backend.
The zone-based model adds conceptual overhead initially but provides better organization for complex network configurations. Understanding both philosophies helps, but seeing them side by side makes the practical differences clearer.
Comparing approaches and capabilities
These tools operate at different abstraction levels, which affects everything about how you use them:
| Aspect | iptables | firewalld |
|---|---|---|
| Abstraction level | Low-level packet filtering | High-level zone management |
| Primary model | Chains and rules | Zones and services |
| IPv4/IPv6 handling | Separate commands | Unified interface |
| Configuration persistence | Manual save/restore | Automatic |
| Runtime changes | Immediate, permanent | Separate runtime/permanent |
| Service definitions | None | XML-based service files |
| Zone concept | Not supported | Core organizational principle |
| Default policies | Per-chain policies | Per-zone defaults |
| Rule inspection | List chains with rules | List zones with services |
| Backend flexibility | Direct netfilter interface | Can use iptables or nftables |
| NetworkManager integration | None | Automatic zone switching |
| Rich rules | Not a concept | Multi-criteria rules |
| Logging | Manual rule configuration | Integrated per-zone logging |
| Desktop integration | None | firewall-config GUI |
| Distribution adoption | Universal | RHEL, Fedora, CentOS, SUSE |
These architectural differences become concrete when you start creating actual firewall rules.
Writing rules with iptables
Iptables requires understanding its structure before you can create even simple rules. You work directly with chains, matches, and targets.
Setting default policies determines what happens to packets that don't match any rules:
sudo iptables -P INPUT DROP
sudo iptables -P FORWARD DROP
sudo iptables -P OUTPUT ACCEPT
These commands set the INPUT and FORWARD chains to drop unmatched packets while allowing all outgoing traffic.
Allowing SSH access:
sudo iptables -A INPUT -p tcp --dport 22 -j ACCEPT
Allowing established connections prevents you from needing rules for every response packet:
sudo iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
Restricting SSH to a specific network:
sudo iptables -A INPUT -p tcp -s 192.168.1.0/24 --dport 22 -j ACCEPT
Viewing your configuration:
sudo iptables -L -v -n
Chain INPUT (policy DROP 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
421 33680 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:22
1524 121920 ACCEPT all -- * * 0.0.0.0/0 0.0.0.0/0 ctstate RELATED,ESTABLISHED
Deleting rules requires knowing their position:
sudo iptables -L INPUT --line-numbers
sudo iptables -D INPUT 1
For IPv6, you need separate commands that mirror your IPv4 rules. Maintaining parallel rulesets doubles your work and creates opportunities for inconsistencies.
Having seen iptables' manual approach, examining firewalld shows how zone-based organization changes the workflow.
Managing zones with firewalld
Firewalld's zone system requires a different mental model but provides better organization for complex networks.
Checking active zones and their interfaces:
sudo firewall-cmd --get-active-zones
public
interfaces: eth0
internal
interfaces: eth1
Checking what's allowed in a zone:
sudo firewall-cmd --zone=public --list-all
public (active)
target: default
icmp-block-inversion: no
interfaces: eth0
sources:
services: dhcpv6-client ssh
ports:
protocols:
forward: no
masquerade: no
Adding a service to a zone (runtime only):
sudo firewall-cmd --zone=public --add-service=http
Making it permanent:
sudo firewall-cmd --zone=public --add-service=http --permanent
Or make all runtime changes permanent at once:
sudo firewall-cmd --runtime-to-permanent
Opening a specific port:
sudo firewall-cmd --zone=public --add-port=8080/tcp --permanent
sudo firewall-cmd --reload
Restricting access with rich rules:
sudo firewall-cmd --zone=public --add-rich-rule='rule family="ipv4" source address="192.168.1.0/24" service name="ssh" accept' --permanent
Creating a custom service requires an XML file:
<?xml version="1.0" encoding="utf-8"?>
<service>
<short>MyApp</short>
<description>Custom application requiring multiple ports</description>
<port protocol="tcp" port="8080"/>
<port protocol="tcp" port="8443"/>
</service>
After reloading, you can use it like any built-in service. The zone-based workflow takes more setup initially but scales better as your network grows more complex.
Advanced networking features
Where iptables and firewalld truly differ is in handling complex scenarios.
Port forwarding with iptables requires working with the nat table:
sudo iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 80 -j DNAT --to-destination 192.168.1.100:80
sudo iptables -A FORWARD -p tcp -d 192.168.1.100 --dport 80 -j ACCEPT
Firewalld handles the same operation with one command:
sudo firewall-cmd --zone=public --add-forward-port=port=80:proto=tcp:toport=8080:toaddr=192.168.1.100 --permanent
Masquerading (NAT for internet sharing) with iptables:
sudo iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
sudo iptables -A FORWARD -i eth1 -o eth0 -j ACCEPT
With firewalld:
sudo firewall-cmd --zone=external --add-masquerade --permanent
Rate limiting in iptables uses the recent module:
sudo iptables -A INPUT -p tcp --dport 22 -m conntrack --ctstate NEW -m recent --set
sudo iptables -A INPUT -p tcp --dport 22 -m conntrack --ctstate NEW -m recent --update --seconds 60 --hitcount 4 -j DROP
Firewalld's rich rules provide complex matching in a more readable format:
sudo firewall-cmd --zone=public --add-rich-rule='rule family="ipv4" source address="10.0.0.0/8" service name="ssh" log prefix="SSH-10NET" level="info" accept' --permanent
Firewalld's abstractions make common operations simpler but give you less control over exactly how packets are processed. With capabilities covered, examining persistence shows different operational approaches.
Configuration persistence and management
Iptables rules exist only in memory until you explicitly save them. After making changes, you need distribution-specific commands to save them.
On Debian and Ubuntu:
sudo apt install iptables-persistent
sudo netfilter-persistent save
Or manually:
sudo iptables-save > /etc/iptables/rules.v4
The saved file format is iptables-restore's input format, which you can edit directly for bulk changes.
Firewalld handles persistence automatically. Every change you make with the --permanent flag gets written immediately:
sudo firewall-cmd --zone=public --add-service=http --permanent
Configuration lives in XML files in /etc/firewalld/zones/:
<?xml version="1.0" encoding="utf-8"?>
<zone>
<short>Public</short>
<service name="ssh"/>
<service name="dhcpv6-client"/>
<service name="http"/>
</zone>
Backing up with iptables:
sudo iptables-save > /backup/firewall-$(date +%Y%m%d).rules
With firewalld:
sudo tar -czf /backup/firewalld-$(date +%Y%m%d).tar.gz /etc/firewalld
Both formats version control cleanly with Git, though firewalld's XML is more human-readable while iptables-save format is more compact.
Integration and automation
Iptables operates independently of most system services. Logging goes through the kernel to syslog, which provides detailed packet information but isn't easy to parse.
Firewalld integrates more deeply with modern Linux. NetworkManager can automatically switch firewall zones when you connect to different networks. Logging connects through journald, making firewall events appear alongside other system events.
Both tools work with configuration management. Ansible example for iptables:
- name: Allow SSH
iptables:
chain: INPUT
protocol: tcp
destination_port: 22
jump: ACCEPT
- name: Save rules
shell: netfilter-persistent save
Ansible example for firewalld:
- name: Allow SSH
firewalld:
service: ssh
permanent: yes
state: enabled
- name: Reload firewalld
command: firewall-cmd --reload
The firewalld module maps more naturally to firewalld's concepts, while iptables requires thinking about chains and rules even in Ansible.
Learning curves and expertise requirements
Iptables demands understanding of networking fundamentals - chains, tables, connection states, default policies, and rule evaluation order. Most people need several days before feeling comfortable, and weeks or months for complex scenarios.
The command syntax is dense:
sudo iptables -A INPUT -p tcp -s 192.168.1.0/24 -m tcp --dport 8080 -m conntrack --ctstate NEW,ESTABLISHED -j ACCEPT
Firewalld has a gentler curve. The zone concept takes getting used to, but basic operations make intuitive sense:
sudo firewall-cmd --zone=public --add-service=http
Most people can secure a basic system within an hour or two. However, rich rules can be as complicated as iptables commands, just with different syntax.
For building deep networking expertise, iptables forces you to learn fundamentals. Firewalld lets you accomplish security tasks without necessarily understanding packet-level details.
Final thoughts
This article looked at iptables and firewalld as two different ways to manage the same underlying firewall system.
Iptables stays important when you need full control and deep network knowledge, and its wide availability makes it a reliable base everywhere. Firewalld builds on top of that with zones and simpler concepts, which fits better when teams are mixed and networks are more complex.
In practice, many setups use both: firewalld for day-to-day management, iptables for special cases. The right choice depends on your distribution, your team’s skills, and how much low-level control you really need.