15%

Save 15% on All Hosting Services

Test your skills and get Discount on any hosting plan

Use code:

Skills
Get Started
21.10.2024
3 +1

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:

ZoneDefault PolicyTypical Use Case
β€”β€”β€”
`trusted`Accept allInternal lab networks, loopback
`home`Reject, selected services allowedHome LAN with known devices
`internal`Reject, selected services allowedInternal corporate network segment
`work`Reject, selected services allowedOffice network, moderate trust
`public`Reject, SSH/DHCPv6 allowedInternet-facing interfaces
`external`Reject, masquerade enabledRouter/NAT gateway external leg
`dmz`Reject, SSH allowedDemilitarized zone servers
`block`Reject with ICMP admin-prohibitedExplicit rejection with response
`drop`Drop silentlyHostile 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-services

To inspect the XML definition of a specific service:

cat /usr/lib/firewalld/services/https.xml

Rich 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' --permanent
firewall-cmd --zone=public --add-rich-rule='
  rule family="ipv4"
  service name="ssh"
  drop' --permanent

Edge 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.

DimensionRuntimePermanent
β€”β€”β€”
Storage locationIn-memory (daemon state)`/etc/firewalld/` XML files
When appliedImmediatelyAfter `–reload` or reboot
Survives rebootNoYes
Safe to testYesRequires reload to verify
RiskLost on restartPersists across reboots

Best practice workflow for production changes:

  1. Apply the rule at runtime only (no --permanent flag) and verify it behaves as expected.
  2. If correct, apply the same rule with --permanent to write it to disk.
  3. Run firewall-cmd --reload to 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 firewalld

Debian / Ubuntu:

sudo apt-get update && sudo apt-get install firewalld

Note for Ubuntu users: If ufw is active, disable it before enabling Firewalld to avoid conflicting netfilter rules:

sudo ufw disable
sudo systemctl disable ufw

Start and enable the daemon:

sudo systemctl start firewalld
sudo systemctl enable firewalld

Verify the daemon is running and the kernel backend is active:

sudo firewall-cmd --state
sudo firewall-cmd --info-zone=public

Essential Firewalld Commands

Inspect Current State

Check daemon status:

sudo firewall-cmd --state

List all active zones with their assigned interfaces and sources:

sudo firewall-cmd --get-active-zones

Display the complete ruleset for a specific zone:

sudo firewall-cmd --zone=public --list-all

Display the complete ruleset for all zones simultaneously:

sudo firewall-cmd --list-all-zones

Manage 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=public

Assign an Interface to a Zone

sudo firewall-cmd --zone=internal --change-interface=eth1 --permanent
sudo firewall-cmd --reload

Pitfall: 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 internal

Add and Remove Services

Allow HTTP in the public zone at runtime:

sudo firewall-cmd --zone=public --add-service=http

Make it permanent:

sudo firewall-cmd --zone=public --add-service=http --permanent

Remove a service:

sudo firewall-cmd --zone=public --remove-service=http --permanent

Open and Close Specific Ports

Open TCP port 8080 at runtime:

sudo firewall-cmd --zone=public --add-port=8080/tcp

Open a UDP port range permanently:

sudo firewall-cmd --zone=public --add-port=60000-61000/udp --permanent

Remove a port:

sudo firewall-cmd --zone=public --remove-port=8080/tcp --permanent

IP Address Allowlisting and Blocklisting

Allow all traffic from a trusted management subnet:

sudo firewall-cmd --zone=trusted --add-source=10.0.0.0/24 --permanent

Block all traffic from a specific IP (source-based drop):

sudo firewall-cmd --zone=drop --add-source=203.0.113.45/32 --permanent

Port 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 --reload

IP 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 --reload

Reload and Synchronize Configuration

Apply all permanent changes to the running state without dropping connections:

sudo firewall-cmd --reload

Perform a complete restart (drops all connections β€” use only in emergencies):

sudo systemctl restart firewalld

Creating 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 --reload

Custom 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 --reload

Firewalld vs. Alternatives: Choosing the Right Tool

FeatureFirewalldUFWiptables (direct)nftables (direct)
β€”β€”β€”β€”β€”
Dynamic rule updatesYesNo (requires reload)NoNo
Zone-based modelYesNoNoNo
D-Bus / API integrationYesNoNoNo
Rich rule / conditional logicYesLimitedYesYes
Default on RHEL familyYesNoLegacyYes (backend)
Default on UbuntuNoYesLegacyYes (backend)
Learning curveModerateLowHighHigh
Suitable for scriptingYesYesYesYes
GUI management toolYes (firewall-config)NoNoNo

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 --reload

If 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 --reload

Logging 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 --reload

Logs 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 --reload

Problem: 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 --reload

Problem: 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 50

Problem: 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 recent

Problem: 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 (FirewallBackend in /etc/firewalld/firewalld.conf) matches your kernel's nftables/iptables support.
  • Verify no conflicting firewall tools (UFW, CSF, direct iptables scripts) 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 --permanent and --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 drop zone 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 --reload and 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.

15%

Save 15% on All Hosting Services

Test your skills and get Discount on any hosting plan

Use code:

Skills
Get Started