firewalld is often seen as a more user-friendly, dynamic replacement for iptables, but the truth is firewalld uses iptables (or nftables) under the hood.
Let’s see firewalld in action. Imagine you have a web server and need to allow HTTP traffic on port 80.
sudo firewall-cmd --zone=public --add-service=http --permanent
sudo firewall-cmd --reload
sudo firewall-cmd --zone=public --list-services
Output:
http
ssh
Here, we told firewalld to permanently allow the http service in the public zone, then reloaded its configuration to apply the change, and finally listed the services currently allowed. It’s a lot cleaner than crafting raw iptables rules for port 80.
The core problem firewalld solves is managing firewall rules in a way that’s easier to understand and modify, especially in dynamic environments. Instead of thinking about individual chains and rules, you think about "zones" and "services" or specific ports. Zones are pre-defined sets of rules that can be applied to network interfaces. Common zones include public (for untrusted networks), home (for trusted home networks), and trusted (for completely trusted networks). Services are predefined configurations for common applications, like ssh or http, which automatically map to the correct ports and protocols.
Internally, firewalld acts as a daemon (firewalld) that manages the actual firewall rules, typically using the iptables or nftables backend. When you issue a command like firewall-cmd --add-service=http, firewalld translates this into the appropriate iptables (or nftables) commands and applies them. The --permanent flag writes these changes to configuration files (usually in /etc/firewalld/zones/), so they persist across reboots. --reload tells the firewalld daemon to re-read its configuration and apply any pending changes to the running kernel firewall.
The exact levers you control are primarily through zones, services, ports, and rich rules. Zones provide a high-level abstraction. Services are convenient shortcuts. Ports allow you to open specific TCP or UDP ports not covered by a predefined service (e.g., sudo firewall-cmd --zone=public --add-port=8080/tcp --permanent). Rich rules offer more granular control, allowing you to specify source/destination IP addresses, protocols, and even packet logging (e.g., sudo firewall-cmd --zone=public --add-rich-rule='rule family="ipv4" source address="192.168.1.100/32" port port="22" protocol="tcp" accept' --permanent).
When you add a service or port to a zone, firewalld doesn’t immediately recompile your entire iptables rule set. Instead, it often uses iptables’s conntrack capabilities and the ipset module to efficiently manage dynamic rules. For example, when you allow ssh, firewalld might add an entry to an ipset list that iptables rules then reference, allowing for rapid addition and removal of allowed IPs without altering the core iptables chain structure itself. This is a key reason for its perceived "dynamism" – it can update firewall state without necessarily triggering a full rule reload that might briefly interrupt established connections.
The most surprising thing is that firewalld’s "runtime" configuration is entirely separate from its "permanent" configuration. When you make changes with --permanent, they are written to files. When you run firewall-cmd --reload, the firewalld daemon reads these files and applies them to the kernel’s firewall. However, you can also make changes directly to the running firewall without touching the permanent configuration. For example, sudo firewall-cmd --zone=public --add-port=8080/tcp applies that rule immediately but it will be lost on the next reload or reboot. This is useful for testing changes before committing them permanently.
The next concept you’ll likely encounter is integrating firewalld with network interface management tools like NetworkManager.