An Introduction to Firewalld: Dynamic Firewall Management on Linux
Firewalld is a userspace firewall management daemon for Linux that provides a dynamic, zone-based interface over the kernel-level packet filtering backends iptables and nftables. Unlike static firewall tools that require a full service restart to apply rule changes, Firewalld modifies netfilter rules on the fly β preserving active TCP sessions and eliminating downtime during policy updates.
This guide covers every operational layer of Firewalld: its architectural model, zone and service abstractions, rich rules, runtime versus permanent configuration, and the exact commands you need to manage a production server securely.
Why Firewalld Replaced Static iptables Workflows
Traditional iptables management means writing rules into shell scripts or flat config files, then flushing and reloading the entire ruleset whenever a change is needed. On a busy server, that flush-and-reload cycle drops in-flight connections and introduces a brief window where no filtering is active.
Firewalld solves this through a D-Bus-activated daemon (firewalld) that holds the authoritative rule state and communicates changes to the kernel incrementally. The result is atomic rule updates with zero connection disruption β critical for any server running persistent workloads such as databases, VPN tunnels, or long-lived API connections.
When you provision a VPS Hosting environment and need to harden it without rebooting or interrupting services, Firewalld is the natural operational choice on RHEL-family and many Debian-family distributions.
Core Architecture: How Firewalld Interacts with the Kernel
Understanding the stack beneath Firewalld prevents misconfiguration and helps you debug unexpected behavior.
User Space
βββββββββββββββββββββββββββββββββββββββββββββββ
β firewall-cmd / firewall-config / D-Bus API β
ββββββββββββββββββββββ¬βββββββββββββββββββββββββ
β D-Bus
ββββββββββββββββββββββΌβββββββββββββββββββββββββ
β firewalld daemon β
β (zone engine, service definitions, rich β
β rule parser, direct rule passthrough) β
ββββββββββββββββββββββ¬βββββββββββββββββββββββββ
β nftables / iptables backend
Kernel Space
ββββββββββββββββββββββΌβββββββββββββββββββββββββ
β netfilter (kernel module) β
βββββββββββββββββββββββββββββββββββββββββββββββSince RHEL 8 and Fedora 32, Firewalld defaults to the nftables backend. On older systems or explicitly configured environments it uses the iptables backend. You can inspect or override the active backend in /etc/firewalld/firewalld.conf via the FirewallBackend directive.
Critical pitfall: Never mix direct iptables or nft commands with Firewalld on the same interface. Firewalld owns the netfilter tables it creates; manual rules inserted outside the daemon will be silently overwritten on the next reload.
Key Concepts in Firewalld
Zones
A zone is a named trust level applied to a network interface or source address range. Every packet entering the system is matched against the zone assigned to its ingress interface, and the zone's ruleset determines what is permitted.
Firewalld ships with the following predefined zones, ordered from most to least permissive:
| Zone | Default Policy | Typical Use Case |
|---|---|---|
| β | β | β |
| `trusted` | Accept all | Internal lab networks, loopback |
| `home` | Reject, selected services allowed | Home LAN with known devices |
| `internal` | Reject, selected services allowed | Internal corporate network segment |
| `work` | Reject, selected services allowed | Office network, moderate trust |
| `public` | Reject, SSH/DHCPv6 allowed | Internet-facing interfaces |
| `external` | Reject, masquerade enabled | Router/NAT gateway external leg |
| `dmz` | Reject, SSH allowed | Demilitarized zone servers |
| `block` | Reject with ICMP admin-prohibited | Explicit rejection with response |
| `drop` | Drop silently | Hostile sources, maximum stealth |
Architectural nuance: A zone can be bound to a network interface (e.g., eth0) or to a source CIDR (e.g., 192.168.1.0/24). Source-based binding takes precedence over interface-based binding, which lets you apply different policies to traffic arriving on the same physical interface from different subnets β a pattern common in multi-tenant or containerized environments.
Services
A service in Firewalld is an XML definition file stored under /usr/lib/firewalld/services/ (system defaults) or /etc/firewalld/services/ (user overrides). Each file declares the ports, protocols, and optional helper modules required for a named application.
For example, the https service definition opens TCP port 443 and loads no additional kernel helpers. The ftp service opens TCP port 21 and loads the nf_conntrack_ftp helper to handle the FTP data channel's dynamic port negotiation.
Using service names instead of raw port numbers makes your firewall policy self-documenting and reduces the risk of typos that leave a port unintentionally open or closed.
To list all available service definitions on your system:
firewall-cmd --get-servicesTo inspect the XML definition of a specific service:
cat /usr/lib/firewalld/services/https.xmlRich Rules
Rich rules extend the zone model with conditional logic that the simple service/port abstraction cannot express. They support matching on source and destination addresses, port ranges, protocols, time windows, and connection state, and they can trigger actions including accept, drop, reject, log, and audit.
Rich rule syntax follows a structured grammar:
rule [family="ipv4|ipv6"]
[source address="addr[/mask]" [invert="true"]]
[destination address="addr[/mask]" [invert="true"]]
[service name="service-name"] | [port port="port" protocol="tcp|udp"]
[log [prefix="prefix"] [level="level"] [limit value="rate/duration"]]
[audit]
[accept|drop|reject [type="reject-type"]]A practical example β rate-limit SSH login attempts to 3 per minute from any single IPv4 source, then log and drop the excess:
firewall-cmd --zone=public --add-rich-rule='
rule family="ipv4"
service name="ssh"
log prefix="SSH_RATELIMIT " level="warning" limit value="3/m"
accept' --permanentfirewall-cmd --zone=public --add-rich-rule='
rule family="ipv4"
service name="ssh"
drop' --permanentEdge case: Rich rule ordering matters. Firewalld evaluates rich rules in the order they were added within a zone. If you add a broad drop rule before a specific accept rule, the accept will never be reached. Always add more specific rules first.
Runtime vs. Permanent Configuration
This is the most operationally important distinction in Firewalld and the source of the most common production mistakes.
| Dimension | Runtime | Permanent |
|---|---|---|
| β | β | β |
| Storage location | In-memory (daemon state) | `/etc/firewalld/` XML files |
| When applied | Immediately | After `βreload` or reboot |
| Survives reboot | No | Yes |
| Safe to test | Yes | Requires reload to verify |
| Risk | Lost on restart | Persists across reboots |
Best practice workflow for production changes:
- Apply the rule at runtime only (no
--permanentflag) and verify it behaves as expected. - If correct, apply the same rule with
--permanentto write it to disk. - Run
firewall-cmd --reloadto synchronize the permanent config into the runtime state and confirm parity.
This workflow prevents the classic scenario where an administrator adds a --permanent rule, reloads, and discovers they have locked themselves out of SSH β because the runtime test would have revealed the problem before it became permanent.
Installing and Enabling Firewalld
Firewalld is installed by default on RHEL, CentOS Stream, Fedora, AlmaLinux, and Rocky Linux. On Debian and Ubuntu it must be installed explicitly.
RHEL / CentOS Stream / AlmaLinux / Rocky Linux:
sudo dnf install firewalldDebian / Ubuntu:
sudo apt-get update && sudo apt-get install firewalldNote for Ubuntu users: If ufw is active, disable it before enabling Firewalld to avoid conflicting netfilter rules:
sudo ufw disable
sudo systemctl disable ufwStart and enable the daemon:
sudo systemctl start firewalld
sudo systemctl enable firewalldVerify the daemon is running and the kernel backend is active:
sudo firewall-cmd --state
sudo firewall-cmd --info-zone=publicEssential Firewalld Commands
Inspect Current State
Check daemon status:
sudo firewall-cmd --stateList all active zones with their assigned interfaces and sources:
sudo firewall-cmd --get-active-zonesDisplay the complete ruleset for a specific zone:
sudo firewall-cmd --zone=public --list-allDisplay the complete ruleset for all zones simultaneously:
sudo firewall-cmd --list-all-zonesManage the Default Zone
The default zone is applied to any interface not explicitly assigned to another zone:
sudo firewall-cmd --get-default-zone
sudo firewall-cmd --set-default-zone=publicAssign an Interface to a Zone
sudo firewall-cmd --zone=internal --change-interface=eth1 --permanent
sudo firewall-cmd --reloadPitfall: On systems using NetworkManager, interface-to-zone assignments made via firewall-cmd may be overridden by the NetworkManager connection profile on reconnect. Set the zone persistently in the NetworkManager connection:
nmcli connection modify "Wired connection 1" connection.zone internalAdd and Remove Services
Allow HTTP in the public zone at runtime:
sudo firewall-cmd --zone=public --add-service=httpMake it permanent:
sudo firewall-cmd --zone=public --add-service=http --permanentRemove a service:
sudo firewall-cmd --zone=public --remove-service=http --permanentOpen and Close Specific Ports
Open TCP port 8080 at runtime:
sudo firewall-cmd --zone=public --add-port=8080/tcpOpen a UDP port range permanently:
sudo firewall-cmd --zone=public --add-port=60000-61000/udp --permanentRemove a port:
sudo firewall-cmd --zone=public --remove-port=8080/tcp --permanentIP Address Allowlisting and Blocklisting
Allow all traffic from a trusted management subnet:
sudo firewall-cmd --zone=trusted --add-source=10.0.0.0/24 --permanentBlock all traffic from a specific IP (source-based drop):
sudo firewall-cmd --zone=drop --add-source=203.0.113.45/32 --permanentPort Forwarding
Forward external TCP port 2222 to internal SSH port 22 (useful for obscuring the default SSH port without changing sshd_config):
sudo firewall-cmd --zone=public --add-forward-port=port=2222:proto=tcp:toport=22 --permanent
sudo firewall-cmd --reloadIP Masquerading (NAT)
Enable masquerade on the external zone to allow a VPS acting as a gateway to NAT traffic from a private subnet:
sudo firewall-cmd --zone=external --add-masquerade --permanent
sudo firewall-cmd --reloadReload and Synchronize Configuration
Apply all permanent changes to the running state without dropping connections:
sudo firewall-cmd --reloadPerform a complete restart (drops all connections β use only in emergencies):
sudo systemctl restart firewalldCreating Custom Zones and Service Definitions
Custom Zone
sudo firewall-cmd --new-zone=management --permanent
sudo firewall-cmd --zone=management --add-source=10.10.0.0/16 --permanent
sudo firewall-cmd --zone=management --add-service=ssh --permanent
sudo firewall-cmd --zone=management --add-service=cockpit --permanent
sudo firewall-cmd --reloadCustom Service Definition
Create a service file for a custom application listening on TCP 9200 (e.g., Elasticsearch):
sudo nano /etc/firewalld/services/elasticsearch.xml<?xml version="1.0" encoding="utf-8"?>
<service>
<short>Elasticsearch</short>
<description>Elasticsearch HTTP API and transport port</description>
<port protocol="tcp" port="9200"/>
<port protocol="tcp" port="9300"/>
</service>sudo firewall-cmd --reload
sudo firewall-cmd --zone=internal --add-service=elasticsearch --permanent
sudo firewall-cmd --reloadFirewalld vs. Alternatives: Choosing the Right Tool
| Feature | Firewalld | UFW | iptables (direct) | nftables (direct) |
|---|---|---|---|---|
| β | β | β | β | β |
| Dynamic rule updates | Yes | No (requires reload) | No | No |
| Zone-based model | Yes | No | No | No |
| D-Bus / API integration | Yes | No | No | No |
| Rich rule / conditional logic | Yes | Limited | Yes | Yes |
| Default on RHEL family | Yes | No | Legacy | Yes (backend) |
| Default on Ubuntu | No | Yes | Legacy | Yes (backend) |
| Learning curve | Moderate | Low | High | High |
| Suitable for scripting | Yes | Yes | Yes | Yes |
| GUI management tool | Yes (firewall-config) | No | No | No |
For teams managing Dedicated Servers at scale, Firewalld's D-Bus API enables programmatic rule management from configuration management tools like Ansible (ansible.posix.firewalld module) or Puppet, which is a significant operational advantage over maintaining raw iptables scripts.
Practical Security Hardening Patterns
Locking Down a Web Server
A typical configuration for a server running HTTPS with SSH restricted to a management IP:
# Set the default zone
sudo firewall-cmd --set-default-zone=public --permanent
# Allow HTTPS globally
sudo firewall-cmd --zone=public --add-service=https --permanent
# Allow HTTP only to redirect to HTTPS (optional)
sudo firewall-cmd --zone=public --add-service=http --permanent
# Restrict SSH to a specific management IP only
sudo firewall-cmd --zone=public --remove-service=ssh --permanent
sudo firewall-cmd --zone=public --add-rich-rule='
rule family="ipv4"
service name="ssh"
source address="198.51.100.10/32"
accept' --permanent
sudo firewall-cmd --reloadIf you are running a web server alongside an SSL Certificates deployment, ensuring port 443 is open in the correct zone before certificate issuance (especially for ACME HTTP-01 or TLS-ALPN-01 challenges) is a prerequisite step that is frequently overlooked.
Protecting a Mail Server
For servers running Email Hosting stacks (Postfix, Dovecot), the required service set is:
sudo firewall-cmd --zone=public --add-service=smtp --permanent
sudo firewall-cmd --zone=public --add-service=smtps --permanent
sudo firewall-cmd --zone=public --add-service=imap --permanent
sudo firewall-cmd --zone=public --add-service=imaps --permanent
sudo firewall-cmd --zone=public --add-service=pop3s --permanent
sudo firewall-cmd --reloadLogging Dropped Packets for Forensics
sudo firewall-cmd --zone=public --add-rich-rule='
rule family="ipv4"
log prefix="DROPPED_PUBLIC " level="warning" limit value="10/m"
drop' --permanent
sudo firewall-cmd --reloadLogs appear in /var/log/messages or the systemd journal (journalctl -k -g DROPPED_PUBLIC). Limit the log rate (as shown above) to prevent log flooding under a DDoS scenario.
Firewalld on cPanel-Managed VPS
If you are using a VPS with cPanel, be aware that cPanel installs and manages its own firewall layer (CSF/LFD by default). Running Firewalld alongside CSF without explicit coordination will produce conflicting netfilter rules. The recommended approach is to choose one firewall management layer per server and disable the other, or to use Firewalld's direct rule passthrough interface to integrate with cPanel's requirements.
Troubleshooting Common Firewalld Issues
Problem: Rule added with --permanent is not in effect.
Cause: Permanent rules require a reload to enter the runtime state.
Fix:
sudo firewall-cmd --reloadProblem: SSH connection dropped after firewall change.
Cause: The SSH service was removed from the active zone, or a drop rich rule was added before the accept rule.
Fix: Access the server via out-of-band console (your hosting provider's VNC/KVM console), then restore the SSH service:
sudo firewall-cmd --zone=public --add-service=ssh --permanent
sudo firewall-cmd --reloadProblem: firewall-cmd returns FirewallD is not running.
Cause: The daemon crashed or was never started.
Fix:
sudo systemctl start firewalld
sudo journalctl -u firewalld -n 50Problem: Rules appear correct but traffic is still blocked.
Cause: A conflicting iptables or nft rule exists outside Firewalld's managed tables, or SELinux/AppArmor is blocking the connection at the application layer.
Fix: Check for manual rules and SELinux denials:
sudo iptables -L -n -v
sudo ausearch -m avc -ts recentProblem: Interface not assigned to expected zone after reboot.
Cause: NetworkManager connection profile overrides Firewalld's interface assignment.
Fix: Set the zone in the NetworkManager profile as described in the interface assignment section above.
Decision Matrix and Technical Checklist
Use this checklist before deploying Firewalld on a production server:
- Confirm the active firewall backend (
FirewallBackendin/etc/firewalld/firewalld.conf) matches your kernel's nftables/iptables support. - Verify no conflicting firewall tools (UFW, CSF, direct
iptablesscripts) are active on the same interfaces. - Assign every network interface explicitly to a zone β never rely solely on the default zone for multi-homed servers.
- Apply all changes at runtime first, verify connectivity, then commit with
--permanentand--reload. - Restrict SSH access to specific source IPs via rich rules before removing the SSH service from the public zone.
- Add rate-limiting rich rules for all publicly exposed authentication services (SSH, SMTP, HTTPS login endpoints).
- Enable logging for the
dropzone with a rate limit to capture threat intelligence without flooding storage. - For servers managed via VPS Control Panels, confirm the control panel's required ports are whitelisted before applying a restrictive default policy.
- Test the permanent configuration by running
firewall-cmd --reloadand immediately verifying all critical services remain reachable. - Document every custom zone, rich rule, and service definition in version control alongside your infrastructure-as-code.
FAQ
What is the difference between --reload and sudo systemctl restart firewalld?
--reload applies permanent configuration changes to the running daemon without dropping established connections. systemctl restart fully restarts the daemon process, which flushes all netfilter rules and briefly interrupts active connections. Always prefer --reload on production systems.
Can Firewalld and iptables coexist on the same server?
Not safely on the same interface. Firewalld manages its own netfilter chains; direct iptables commands that modify the same chains will be overwritten on the next Firewalld reload. If you need to inject custom rules, use Firewalld's --direct interface or rich rules instead.
How do I make a runtime rule permanent without re-typing it?
There is no built-in command to promote all runtime rules to permanent in one step. The correct workflow is to apply each rule twice β once without --permanent for testing, then with --permanent for persistence β followed by --reload. Alternatively, use firewall-cmd --runtime-to-permanent (available in Firewalld 0.9+) to snapshot the current runtime state to disk.
Why is my zone assignment reset after a NetworkManager reconnect?
NetworkManager stores zone assignments in its own connection profiles. A firewall-cmd --change-interface assignment is a runtime override that NetworkManager can overwrite. Persist the assignment with nmcli connection modify <profile-name> connection.zone <zone>.
Does Firewalld support IPv6?
Yes. Firewalld manages both IPv4 and IPv6 netfilter rules simultaneously. Rich rules require the family="ipv6" attribute to target IPv6 traffic specifically. Zone and service rules apply to both address families by default unless the service definition or rich rule restricts the scope.
