26.6 firewalld: Zone-Based Firewall for RHEL/Fedora
Right, so you’ve landed on a system running Red Hat, Fedora, or one of their less-annoying-than-they-used-to-be cousins. Welcome to firewalld. If iptables feels like hand-crafting a wooden chair with precise chisels, firewalld is the IKEA version. It comes with pre-fab pieces (zones, services) and an Allen key (firewall-cmd), and for most people, it gets the job up and running without you needing to be a master carpenter. Its core idea is simple but powerful: instead of writing a bajillion rules, you assign interfaces to zones, and those zones have pre-defined or custom sets of rules. It’s a stateful firewall, so it understands connections, which is 90% of what you need.
The genius, and occasionally the frustration, of firewalld is its obsession with runtime versus permanent configuration. This is its best feature and the thing that will absolutely bite you if you forget it.
The Runtime vs. Permanent Divide
Pay attention, because this is critical. firewalld has two distinct configurations:
- Runtime: The rules currently active in the kernel. This is what’s actually protecting your machine right now.
- Permanent: The rules stored on disk that will be loaded on the next reboot or
firewalldservice reload.
Why this split? It’s a safety net. You can test a rule in the runtime (--add), see if it breaks your SSH connection or your database, and if it does, you can just reload (--reload) to revert to the last permanent config. If you like the change, you make it permanent (--permanent). The catch? If you only use --permanent, your change does nothing until the next reload. I’ve watched seasoned admins bash their heads against this one.
Let’s see it in action. Say you need SSH access.
# This adds the rule to the runtime only. Good for testing.
sudo firewall-cmd --add-service=ssh
# This adds the rule to the permanent config ONLY. It is NOT active yet.
sudo firewall-cmd --permanent --add-service=ssh
# The right way: add it to both runtime and permanent in one go.
sudo firewall-cmd --add-service=ssh --permanent
To see the difference, check the active zone:
# Shows runtime configuration (default output)
sudo firewall-cmd --list-all
# Shows permanent configuration
sudo firewall-cmd --permanent --list-all
Zones: Your Network Personality Matrix
Zones are firewalld’s organizing principle. Each zone defines a trust level for the connections it governs. An interface (or source IP) can only be in one zone at a time. The magic is that an interface can move between zones, instantly applying a different policy. Your Wi-Fi interface might jump from the strict public zone at a coffee shop to the trusted home zone when you connect to your house.
The public zone is the default for any new interface, which is the system’s way of saying, “I don’t trust you yet.” Here are the heavy hitters:
- drop: The “talk to the hand” zone. Incoming connections are dropped without a response. Low resource use, maximally rude.
- block: The “I’m ignoring you” zone. Incoming connections are rejected with an ICMP message. More polite, tells the caller the door is locked.
- public: For untrusted public networks. You allow only what you explicitly say.
- internal/external: For, you know, internal and external networks behind a NAT.
- dmz: For systems facing the public, usually with limited access back to the internal network.
- home/trusted: For networks you… trust. Allows more services like Samba or MDNS.
Assigning an interface is straightforward:
# See which zone your interface is in (probably public)
sudo firewall-cmd --get-active-zones
# Move eth0 to the 'internal' zone permanently
sudo firewall-cmd --permanent --zone=internal --change-interface=eth0
# Reload to apply the permanent change to the runtime
sudo firewall-cmd --reload
Services: The Pre-Fab Rule Sockets
Instead of you memorizing that SSH is port 22/tcp, firewalld provides service definitions. These are XML files in /usr/lib/firewalld/services/ (for default ones) and /etc/firewalld/services/ (for your custom ones). Using them makes your commands self-documenting. --add-service=ssh is clearer than --add-port=22/tcp.
Need to expose a custom web app on port 8080? Don’t just shove a port rule in. Create a service. It’s cleaner and reusable.
# Create a new service definition file
sudo cp /usr/lib/firewalld/services/http.xml /etc/firewalld/services/my-webapp.xml
# Edit it to change the port to 8080
sudo vi /etc/firewalld/services/my-webapp.xml
# Change the <port protocol="tcp" port="80"/> to port="8080"
# Reload firewalld to see the new service
sudo firewall-cmd --reload
# Now add it to your zone. This is so much clearer.
sudo firewall-cmd --permanent --add-service=my-webapp
sudo firewall-cmd --reload
The Reload Landmine and Best Practices
The --reload command is not atomic. It completely flushes the runtime ruleset and rebuilds it from the permanent configuration. This means there is a tiny window where your firewall rules are gone. On a busy machine, this can drop packets. The right way to handle critical changes is to add the rule to the runtime first, then make it permanent.
# 1. Add the rule to the runtime. Test it immediately.
sudo firewall-cmd --zone=public --add-port=8443/tcp
# Does your service still work? Good.
# 2. Now, add it to the permanent config.
sudo firewall-cmd --permanent --zone=public --add-port=8443/tcp
# The rule is now safe across reboots and reloads.
# The WRONG way is to do just the --permanent step and forget to reload, wondering why nothing happens.
firewalld abstracts the nightmare of direct iptables commands into something manageable. Is it as granular? No. Can it do complex iptables owner module tricks? Not really. But for 95% of server and desktop use cases, it’s more than enough. Respect the runtime/permanent split, use zones and services as intended, and it will serve you well without the migraines.