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#
- Overview
- Core Concepts
- 2.1 Zones
- 2.2 Services
- 2.3 Ports
- 2.4 Rich Rules
- 2.5 Runtime vs. Permanent
- Installation and Setup
- 3.1 Installation
- 3.2 Service Management
- Basic Configuration
- 4.1 Managing Zones
- 4.2 Adding Services
- 4.3 Opening Ports
- 4.4 Rich Rules
- Custom Service Files
- NAT and Masquerading
- 6.1 Masquerading
- 6.2 Port Forwarding
- ICMP Filtering
- Advanced Configuration
- 8.1 Allowing VRRP
- 8.2 Direct Rules
- 8.3 Logging Denied Packets
- Testing and Verification
- Troubleshooting
- See Also
- 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:
| Zone | Default Target | Typical Use |
|---|---|---|
drop | DROP | Block everything, no replies |
block | REJECT | Block everything, send rejection |
public | default (reject) | Untrusted networks |
external | default (reject) | External NAT/masqueraded networks |
dmz | default (reject) | DMZ servers with limited access |
work | default (reject) | Work networks |
home | default (reject) | Home networks |
internal | default (reject) | Internal networks |
trusted | ACCEPT | Allow 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-permanent3. 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 firewalld3.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 firewalld4. 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-zones4.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 --reload4.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 --reload4.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-rules5. 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 --reloadA 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=myapp6. 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 --reloadYou 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 --system6.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 --reloadWhen 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 blockedTo 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 --reload7.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 --reload8. 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 --reload8.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 --reload8.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#
| Issue | Cause | Solution |
|---|---|---|
| Rules disappear after reboot | Added without --permanent | Re-add with --permanent flag, then --reload |
| Permanent rules not active | Forgot to reload after --permanent | Run firewall-cmd --reload |
| Service not found | Custom service XML missing or malformed | Check /etc/firewalld/services/, validate XML, reload |
| Interface in wrong zone | Interface auto-assigned to default zone | Explicitly assign with --change-interface and --permanent |
| ICMP block not working as expected | Block inversion is enabled | Check --query-icmp-block-inversion; inversion flips the meaning of the block list |
| Port forward not working | Masquerading not enabled or IP forwarding off | Enable masquerade on the zone and set net.ipv4.ip_forward=1 |
| Rich rule syntax error | Quoting or ordering issue | Use single quotes around the entire rule; check man firewalld.richlanguage |
| FirewallD conflicts with other firewalls | iptables/nftables rules set outside FirewallD | Stop other firewall services; use only FirewallD or only raw nftables |
| Cannot start FirewallD | nftables/iptables backend missing | Install nftables package; check FirewallBackend in /etc/firewalld/firewalld.conf |
See Also#
Sources#
- FirewallD Official Documentation
- FirewallD Rich Language
- Red Hat - Using FirewallD
man firewall-cmdman firewalld.zoneman firewalld.service