LinuxXXV · Firewallsfirewalld
firewalld - zone-based firewall management
What you'll learn
- Describe firewalld zones and how they work
- Configure firewalld with firewall-cmd and firewall-config
- Distinguish runtime from permanent changes and persist them safely
- Recognise when firewalld is the right choice
- Understand the relationship with nftables
Prerequisites
Verified against Ubuntu 24.04 LTS · Debian 12 (Bookworm) · RHEL 9.x · Rocky Linux 9.x · AlmaLinux 9.x · Linux kernel 6.1 LTS / 6.6 LTS · systemd 255+ · OpenSSH 8.7p1 (RHEL 9) / 9.6p1 (Ubuntu 24.04) · nftables 1.0.x · chrony 4.x · Pacemaker 2.1.x · Corosync 3.1.x · 2026-08-09
firewalld is the default firewall management tool on RHEL, Rocky, Alma, and Fedora. It provides a higher-level abstraction (zones, services) over nftables/iptables. This lesson covers what firewalld does, when to use it, and how to configure it.
What firewalld is
firewalld is a daemon that manages nftables (or iptables) rules behind the scenes. You do not write nftables rules directly; you tell firewalld “allow SSH on the public zone” and firewalld translates that into nftables rules.
Key concepts:
- Zones: pre-defined trust levels (
public,trusted,internal,dmz,home,work,block,drop,external). - Services: pre-defined service definitions (
ssh,http,https,dns, etc.) that map to ports. - Interfaces: each interface is bound to one zone.
- Sources: an IP address or subnet can be bound to a zone (overrides interface binding).
Read the current state
firewall-cmd --list-all # default zone
firewall-cmd --list-all-zones # all zones
firewall-cmd --get-default-zone # default zone
firewall-cmd --get-active-zones # zones in use
firewall-cmd --zone=public --list-all # specific zone
Output:
public (active)
target: default
icmp-block-inversion: no
interfaces: eth0
sources:
services: ssh
ports: 22/tcp
protocols:
forward: no
masquerade: no
forward-ports:
source-ports:
icmp-blocks:
rich rules:
The public zone is active, has eth0, and allows ssh
service (TCP/22).
Common commands
# Add a service
sudo firewall-cmd --zone=public --add-service=http
sudo firewall-cmd --zone=public --add-service=https
# Add a port
sudo firewall-cmd --zone=public --add-port=8080/tcp
# Remove a service
sudo firewall-cmd --zone=public --remove-service=http
# Add a source
sudo firewall-cmd --zone=trusted --add-source=10.0.0.0/24
# Reload: re-applies the PERMANENT config and DISCARDS runtime-only changes
sudo firewall-cmd --reload
# Promote the current runtime config to permanent
sudo firewall-cmd --runtime-to-permanent
# Or write the permanent config directly, then reload to load it
sudo firewall-cmd --permanent --zone=public --add-service=http
sudo firewall-cmd --reload
# Safe testing: auto-revert the change after 5 minutes if you do not confirm it
sudo firewall-cmd --zone=public --add-service=http --timeout=5m
By default, changes are runtime only - they revert on reload. There are two ways to persist, and the order matters:
- Change the runtime, verify it works, then run
firewall-cmd --runtime-to-permanentto copy the whole working runtime configuration into/etc/firewalld/. - Or write the change with
--permanentfirst, then--reloadto load the permanent config into the runtime.
--reload is not “save”. It re-reads /etc/firewalld/ and
replaces the runtime with it, so any runtime-only rule is
lost. That distinction bites hardest during a lockout: you
add an allow rule that restores your SSH session, reload out
of habit, and the reload puts the locked-out configuration
straight back. Verify first, then
--runtime-to-permanent.
Check the two views against each other before you walk away:
sudo firewall-cmd --zone=public --list-all # runtime
sudo firewall-cmd --permanent --zone=public --list-all # on disk
The relationship with nftables
firewalld generates nftables rules. You can see them:
sudo nft list tables # inet firewalld
sudo nft list table inet firewalld # everything firewalld manages
Everything firewalld manages lives in a single table named
firewalld in the inet family. Inside it, chains are named
<netfilter table>_<hook> — filter_INPUT, filter_FORWARD,
nat_PREROUTING, mangle_PREROUTING — with a per-zone chain
such as filter_IN_public holding that zone’s rules. Changes to
firewalld translate to additions and removals inside that table.
Knowing the real name matters when you are debugging: grep for a
table called firewalld_filter and you find nothing, which reads
as “firewalld is not managing nftables at all” and sends you off
looking for an iptables backend that is not there.
You can also create a “direct” ruleset that mixes firewalld-managed rules with custom nftables rules:
sudo firewall-cmd --direct --add-rule ipv4 filter INPUT 0 -p tcp --dport 9999 -j ACCEPT
Use --direct sparingly; it bypasses firewalld’s abstractions.
When to use firewalld vs raw nftables
Use firewalld when:
- The host is on RHEL, Rocky, Alma, or Fedora.
- The host has a simple, zone-based firewall (single network, all interfaces same trust).
- Operations are managed via Ansible, Puppet, or similar - firewalld has first-class automation support.
Use raw nftables when:
- The ruleset is complex (multi-zone, custom chains, expressions).
- The host is on Debian or Ubuntu (firewalld is less common).
- You need features firewalld does not expose (e.g. advanced set matching).
Configuration files
firewalld’s configuration lives in:
/usr/lib/firewalld/zones/- default zone definitions./etc/firewalld/zones/- custom zone definitions./etc/firewalld/firewalld.conf- main configuration.
Each zone is an XML file:
<?xml version="1.0" encoding="utf-8"?>
<zone>
<short>Public</short>
<description>For use in public areas.</description>
<service name="ssh"/>
<port protocol="tcp" port="8080"/>
</zone>
To modify, edit the XML or use firewall-cmd.
Rich rules
For complex rules (source-based, port forwarding, logging), firewalld uses “rich rules”:
# Allow SSH from a specific subnet only
sudo firewall-cmd --zone=public --add-rich-rule='
rule family="ipv4" source address="10.0.0.0/24"
service name="ssh" accept'
# Log and reject everything else, rate limited
sudo firewall-cmd --zone=public --add-rich-rule='
rule family="ipv4" log prefix="fw-drop: " level="warning"
limit value="5/m" reject'
Rich rules are translated to nftables rules.
Port forwarding
Port forwarding is not a rich rule; it has its own option, and it must be on one line:
# Forward external 2222 to 10.0.0.5:22
sudo firewall-cmd --zone=external --add-forward-port=port=2222:proto=tcp:toport=22:toaddr=10.0.0.5
Broken across a newline after --add-forward-port=, the shell
sees two commands: firewall-cmd --add-forward-port= with an
empty value, and then an attempt to execute a program called
port=2222:proto=tcp:.... The first fails with an argument
error, the second with “command not found”, and neither message
mentions the forward you were trying to create.
Forwarding to another host also needs masquerade in the zone, or the reply from 10.0.0.5 goes back to the client directly and is dropped as out of state:
sudo firewall-cmd --zone=external --add-masquerade
sudo firewall-cmd --zone=external --list-forward-ports # verify
Knowledge check
Knowledge check · 4 questions
Q1. What is the difference between a zone and a service in firewalld?
Q2. A rule added with firewall-cmd and no --permanent flag is gone after the next reload.
Q3. Which of the following are firewalld zones? Select all that apply.
Q4. You are locked out of a remote host. On the serial console you run firewall-cmd --zone=trusted --add-source=10.0.0.0/24 and SSH starts working again. What must you NOT do next?
Passing score: 75%. Answers are checked in this browser.