Dynamic firewall manager for Linux that uses zones and services to simplify packet filtering on top of nftables (or the legacy iptables backend).

Addresses below are RFC 5737 documentation ranges or placeholders - swap in your own.

Table of Contents#

  1. Overview
  2. Core Concepts
  3. Installation and Setup
  4. Basic Configuration
  5. Custom Service Files
  6. NAT and Masquerading
  7. ICMP Filtering
  8. Advanced Configuration
  9. Testing and Verification
  10. Troubleshooting
  11. See Also
  12. Sources

1. Overview#

FirewallD is a dynamic firewall manager that ships as the default firewall solution on RHEL, CentOS, Fedora, and openSUSE. It provides a D-Bus interface for managing firewall rules at runtime without needing to restart the firewall service, and it uses nftables as its default backend (iptables on older systems).

Key advantages over raw nftables/iptables:

  • Zone-based architecture - assign interfaces and sources to trust zones
  • Runtime and permanent rule separation - test rules before persisting them
  • Service abstractions - allow traffic by service name instead of memorizing port numbers
  • Rich rule language - expressive syntax for complex filtering without writing raw nftables
  • D-Bus API - other applications can query and modify the firewall programmatically

2. Core Concepts#

2.1. Zones#

Zones define the trust level of network connections or interfaces. Each zone has a default behavior (target) and a set of allowed services, ports, and rules. FirewallD ships with several predefined zones:

ZoneDefault TargetTypical Use
dropDROPBlock everything, no replies
blockREJECTBlock everything, send rejection
publicdefault (reject)Untrusted networks
externaldefault (reject)External NAT/masqueraded networks
dmzdefault (reject)DMZ servers with limited access
workdefault (reject)Work networks
homedefault (reject)Home networks
internaldefault (reject)Internal networks
trustedACCEPTAllow all traffic

Each interface or source address range is assigned to exactly one zone. Unassigned interfaces fall into the default zone.

2.2. Services#

Services are predefined rule sets stored as XML files that allow traffic based on a service name. For example, the ssh service opens TCP port 22, and the http service opens TCP port 80.

  • System services: /usr/lib/firewalld/services/
  • Custom/overridden services: /etc/firewalld/services/

2.3. Ports#

Individual ports or port ranges can be opened in a zone when no predefined service exists. Ports are specified as <port>/<protocol> (e.g., 8080/tcp or 5000-6000/udp).

2.4. Rich Rules#

Rich rules provide an expressive language for defining detailed firewall rules, including source/destination filtering, rate limiting, logging, port forwarding, and protocol matching. They follow the syntax:

rule [family="ipv4|ipv6"]
  [source address="<address>[/<mask>]" [invert="true"]]
  [destination address="<address>[/<mask>]" [invert="true"]]
  [service name="<service>"]
  [port port="<port>" protocol="<protocol>"]
  [protocol value="<protocol>"]
  [log [prefix="<prefix>"] [level="<level>"] [limit value="<rate>"]]
  [accept|drop|reject|mark]

2.5. Runtime vs. Permanent#

FirewallD maintains two separate configurations:

  • Runtime - active rules, lost on reload or reboot
  • Permanent - stored on disk, loaded on reload or reboot

The --permanent flag writes to the permanent configuration only. After any --permanent change, you must run firewall-cmd --reload to apply it to the runtime. Alternatively, omit --permanent to test rules at runtime first, then use firewall-cmd --runtime-to-permanent to persist them.

# Permanent change workflow
firewall-cmd --permanent --zone=public --add-service=https
firewall-cmd --reload

# Test-then-persist workflow
firewall-cmd --zone=public --add-service=https
# Verify it works, then:
firewall-cmd --runtime-to-permanent

3. Installation and Setup#

3.1. Installation#

FirewallD comes preinstalled on many distributions. To install manually:

# Debian/Ubuntu
sudo apt install firewalld

# RHEL/CentOS/Fedora
sudo dnf install firewalld

# Arch Linux
sudo pacman -S firewalld

3.2. Service Management#

# Start and enable at boot
sudo systemctl enable --now firewalld

# Check status
sudo systemctl status firewalld
firewall-cmd --state

# Reload rules (apply permanent changes)
sudo firewall-cmd --reload

# Full restart (drops all connections briefly)
sudo systemctl restart firewalld

4. Basic Configuration#

4.1. Managing Zones#

# List all available zones
firewall-cmd --get-zones

# List active zones (zones with assigned interfaces/sources)
firewall-cmd --get-active-zones

# Show default zone
firewall-cmd --get-default-zone

# Set default zone
firewall-cmd --set-default-zone=public

# Assign interface to a zone permanently
firewall-cmd --permanent --zone=internal --add-interface=eth0

# Change interface zone
firewall-cmd --permanent --zone=public --change-interface=eth1

# Assign a source address range to a zone
firewall-cmd --permanent --zone=trusted --add-source=192.0.2.0/24

# Show full configuration for a zone
firewall-cmd --zone=public --list-all

# Show all zones with their full configuration
firewall-cmd --list-all-zones

4.2. Adding Services#

# List available services
firewall-cmd --get-services

# Allow a service (runtime only, for testing)
firewall-cmd --zone=public --add-service=http

# Allow a service permanently
firewall-cmd --permanent --zone=public --add-service=http
firewall-cmd --reload

# Allow multiple services at once
firewall-cmd --permanent --zone=public --add-service={http,https,dns}
firewall-cmd --reload

# Remove a service
firewall-cmd --permanent --zone=public --remove-service=http
firewall-cmd --reload

4.3. Opening Ports#

# Open a single port
firewall-cmd --permanent --zone=public --add-port=8080/tcp
firewall-cmd --reload

# Open a port range
firewall-cmd --permanent --zone=public --add-port=5000-6000/udp
firewall-cmd --reload

# List open ports
firewall-cmd --zone=public --list-ports

# Remove a port
firewall-cmd --permanent --zone=public --remove-port=8080/tcp
firewall-cmd --reload

4.4. Rich Rules#

# Allow SSH from a specific subnet
firewall-cmd --permanent --zone=public --add-rich-rule='rule family="ipv4" source address="192.0.2.0/24" port port=22 protocol=tcp accept'

# Drop traffic from a specific IP
firewall-cmd --permanent --zone=public --add-rich-rule='rule family="ipv4" source address="192.0.2.50" drop'

# Rate-limit SSH connections (max 3 per minute)
firewall-cmd --permanent --zone=public --add-rich-rule='rule service name="ssh" accept limit value="3/m"'

# Log and drop incoming traffic on port 4444
firewall-cmd --permanent --zone=public --add-rich-rule='rule family="ipv4" port port=4444 protocol=tcp log prefix="blocked-4444: " level="warning" limit value="5/m" drop'

# Apply changes
firewall-cmd --reload

# List rich rules for a zone
firewall-cmd --zone=public --list-rich-rules

5. Custom Service Files#

When no predefined service matches your application, create a custom service file in /etc/firewalld/services/. The file is an XML document with the service name, description, and port definitions.

Example: /etc/firewalld/services/myapp.xml

<?xml version="1.0" encoding="utf-8"?>
<service>
  <short>MyApp</short>
  <description>My custom application server</description>
  <port protocol="tcp" port="8443"/>
  <port protocol="tcp" port="8444"/>
</service>

After creating the file, reload and use it:

firewall-cmd --reload
firewall-cmd --permanent --zone=public --add-service=myapp
firewall-cmd --reload

A more complete example with source ports and destination addresses:

<?xml version="1.0" encoding="utf-8"?>
<service>
  <short>Netbird</short>
  <description>Netbird VPN relay and management</description>
  <port protocol="tcp" port="443"/>
  <port protocol="tcp" port="8082"/>
  <port protocol="udp" port="3478"/>
  <port protocol="udp" port="49152-65535"/>
</service>

To see the details of any service:

firewall-cmd --info-service=myapp

6. NAT and Masquerading#

6.1. Masquerading#

Masquerading (source NAT) allows hosts on a private network to access external networks through the firewall's public IP. It is typically enabled on the zone attached to the external interface.

# Enable masquerading on the external zone
firewall-cmd --permanent --zone=external --add-masquerade
firewall-cmd --reload

# Check if masquerading is enabled
firewall-cmd --zone=external --query-masquerade

# Disable masquerading
firewall-cmd --permanent --zone=external --remove-masquerade
firewall-cmd --reload

You must also enable IP forwarding in the kernel:

# Temporary (until reboot)
sudo sysctl -w net.ipv4.ip_forward=1

# Permanent (add to /etc/sysctl.d/99-forward.conf)
echo "net.ipv4.ip_forward = 1" | sudo tee /etc/sysctl.d/99-forward.conf
sudo sysctl --system

6.2. Port Forwarding#

Port forwarding (DNAT) redirects incoming traffic to a different address or port.

# Forward port 80 to internal server 192.0.2.10:8080
firewall-cmd --permanent --zone=public --add-forward-port=port=80:proto=tcp:toaddr=192.0.2.10:toport=8080
firewall-cmd --reload

# Forward port 443 to the same host but different port
firewall-cmd --permanent --zone=public --add-forward-port=port=443:proto=tcp:toport=8443
firewall-cmd --reload

# Forward using a rich rule (more control)
firewall-cmd --permanent --zone=public --add-rich-rule='rule family="ipv4" forward-port port=2222 protocol=tcp to-port=22 to-addr=192.0.2.10'
firewall-cmd --reload

# List port forwards
firewall-cmd --zone=public --list-forward-ports

# Remove a port forward
firewall-cmd --permanent --zone=public --remove-forward-port=port=80:proto=tcp:toaddr=192.0.2.10:toport=8080
firewall-cmd --reload

When forwarding to a different host, masquerading (or SNAT) must be enabled on the same zone so return traffic routes correctly.

7. ICMP Filtering#

7.1. ICMP Block Inversion#

FirewallD's ICMP filtering has an important concept called block inversion. By default, the ICMP block list acts as a blocklist: types on the list are blocked, everything else is allowed. When block inversion is enabled, the behavior inverts: types on the list become the only ones allowed, and everything else is blocked.

# Without inversion (default): block specific ICMP types
firewall-cmd --permanent --zone=public --add-icmp-block=echo-request
firewall-cmd --reload
# Result: echo-request is blocked, all other ICMP types are allowed

# With inversion: only listed types are allowed, rest blocked
firewall-cmd --permanent --zone=public --add-icmp-block-inversion
firewall-cmd --permanent --zone=public --add-icmp-block=echo-reply
firewall-cmd --permanent --zone=public --add-icmp-block=destination-unreachable
firewall-cmd --reload
# Result: only echo-reply and destination-unreachable are allowed, rest blocked

To check the current state:

# List supported ICMP types
firewall-cmd --get-icmptypes

# Check if inversion is enabled
firewall-cmd --zone=public --query-icmp-block-inversion

# List blocked ICMP types
firewall-cmd --zone=public --list-icmp-blocks

# Remove block inversion
firewall-cmd --permanent --zone=public --remove-icmp-block-inversion
firewall-cmd --reload

7.2. Blocking Specific ICMP Types#

To selectively block ICMP types without inversion:

# Block ping (echo-request)
firewall-cmd --permanent --zone=public --add-icmp-block=echo-request

# Block timestamp requests
firewall-cmd --permanent --zone=public --add-icmp-block=timestamp-request

# Apply
firewall-cmd --reload

8. Advanced Configuration#

8.1. Allowing VRRP#

To allow Virtual Router Redundancy Protocol (VRRP) traffic through the firewall:

firewall-cmd --permanent --zone=public --add-rich-rule='rule protocol value="vrrp" accept'
firewall-cmd --reload

8.2. Direct Rules#

Direct rules provide a pass-through to the underlying nftables/iptables backend for cases where FirewallD's abstraction is insufficient. Use sparingly, as they bypass zone logic.

# Add a direct rule (iptables syntax)
firewall-cmd --permanent --direct --add-rule ipv4 filter INPUT 0 -p tcp --dport 9090 -j ACCEPT

# List direct rules
firewall-cmd --direct --get-all-rules

# Remove a direct rule
firewall-cmd --permanent --direct --remove-rule ipv4 filter INPUT 0 -p tcp --dport 9090 -j ACCEPT
firewall-cmd --reload

8.3. Logging Denied Packets#

# Enable logging of denied packets (values: off, all, unicast, broadcast, multicast)
firewall-cmd --set-log-denied=unicast

# Check current setting
firewall-cmd --get-log-denied

# Logs appear in the system journal
journalctl -k --grep="FINAL_REJECT"

9. Testing and Verification#

# Reload to apply permanent changes
firewall-cmd --reload

# Show complete zone configuration
firewall-cmd --list-all
firewall-cmd --zone=public --list-all

# Show all zones
firewall-cmd --list-all-zones

# Check if a service is enabled in a zone
firewall-cmd --zone=public --query-service=http

# Test port access from an external host
nc -zv <firewall-ip> 80

# Test with nmap from an external host
nmap -p 22,80,443 <firewall-ip>

# Check the runtime vs. permanent configuration for drift
diff <(firewall-cmd --list-all) <(firewall-cmd --list-all --permanent)

Troubleshooting#

IssueCauseSolution
Rules disappear after rebootAdded without --permanentRe-add with --permanent flag, then --reload
Permanent rules not activeForgot to reload after --permanentRun firewall-cmd --reload
Service not foundCustom service XML missing or malformedCheck /etc/firewalld/services/, validate XML, reload
Interface in wrong zoneInterface auto-assigned to default zoneExplicitly assign with --change-interface and --permanent
ICMP block not working as expectedBlock inversion is enabledCheck --query-icmp-block-inversion; inversion flips the meaning of the block list
Port forward not workingMasquerading not enabled or IP forwarding offEnable masquerade on the zone and set net.ipv4.ip_forward=1
Rich rule syntax errorQuoting or ordering issueUse single quotes around the entire rule; check man firewalld.richlanguage
FirewallD conflicts with other firewallsiptables/nftables rules set outside FirewallDStop other firewall services; use only FirewallD or only raw nftables
Cannot start FirewallDnftables/iptables backend missingInstall nftables package; check FirewallBackend in /etc/firewalld/firewalld.conf

See Also#

Sources#