firewalld is the default host firewall on Red Hat Enterprise Linux, Fedora,
CentOS Stream, Rocky and AlmaLinux. It exposes a two-level declarative
model, zones attached to interfaces and preconfigured services allowed
per zone, on top of nftables (modern Linux) or iptables (older Linux). The
promise: configure rich filtering without writing a rule chain, and
reload on the fly without cutting established connections. This guide
lays the foundations (zones, services, rich rules), handles the two classic
pitfalls (runtime vs permanent, forgetting to allow SSH), and ends with a
rollback plan.
What you will learn
- Install and enable firewalld, check its state
- Understand zones (
public,internal,work…) and their role - Tell runtime from permanent so you no longer lose your rules on reload
- Open services and ports per zone
- Write a rich rule for the cases services do not cover
- Log, troubleshoot and roll back cleanly
In what context?
firewalld is the default when you are in the Red Hat ecosystem (RHEL,
Fedora, CentOS Stream, Rocky, AlmaLinux) or on a modern openSUSE. You will
meet it almost every time on an RHCSA workstation, on a RHEL/Rocky cloud
VM, and on any server managed by firewalld.service.
It is used:
- when you want a host firewall with zones (laptop moving between networks, multi-interface server)
- when you want to reload without cutting established TCP connections
- when you prefer to declare named services (
http,ssh,samba…) rather than port numbers
On Debian/Ubuntu, the default is UFW, simpler and enough for a single-interface server. Do not mix the two on the same machine.
Prerequisites
- A RHEL, Fedora, CentOS Stream, Rocky or AlmaLinux server (or Debian/Ubuntu if you explicitly need firewalld)
- A user with
sudorights - A key-based SSH session and a fallback access (cloud console, IPMI, serial) before any change
Core concept: zones
firewalld attaches each network interface to a zone, and each zone carries its own rules. This is what lets a laptop be strict on public Wi-Fi and permissive on the home Wi-Fi by switching zones automatically.
The standard zones to know, with the open services read from
/usr/lib/firewalld/zones/ on AlmaLinux 10:
| Zone | Typical use | Default policy |
|---|---|---|
drop | Hostile (unknown network, heavy scanning) | Inbound blocked without notification |
block | Hostile with explicit rejection | Inbound blocked, ICMP reject |
public | Internet, public Wi-Fi, frequent default | ssh, dhcpv6-client, cockpit allowed, the rest blocked |
external | Internet egress with masquerade (NAT) | ssh allowed, masquerade enabled |
work | Office LAN | ssh, dhcpv6-client, cockpit |
home | Home LAN | ssh, mdns, samba-client, dhcpv6-client, cockpit |
internal | Trusted LAN | Same as home |
trusted | Everything allowed | No filtering |
The cockpit service (port 9090) is an addition of the Red Hat
distributions: on Debian 13 (firewalld 2.3.1), the same zones do not open
it. Check the default zone and the active zones:
sudo firewall-cmd --get-default-zonesudo firewall-cmd --get-active-zonesRuntime vs permanent, the main pitfall
firewalld maintains two configurations, and almost every surprise comes from mixing them up:
- Runtime: the rules currently applied. Lost at the next
reloadorreboot. - Permanent: the rules saved in
/etc/firewalld/. Applied afterreloadorreboot.
A command without --permanent touches only the runtime. A command with
--permanent touches only the permanent configuration; you then need a
firewall-cmd --reload for it to take effect. Measured on AlmaLinux 10: an
--add-service=https without --permanent disappears at the next
--reload.
Install firewalld
firewalld is preinstalled on the Red Hat family, available but not installed on Debian and Ubuntu. In every case, the daemon is started and enabled at boot with systemd.
firewalld is normally installed and enabled by default:
sudo dnf install -y firewalldsudo systemctl enable --now firewalldCheck:
sudo firewall-cmd --versionsudo firewall-cmd --statefirewall-cmd --state must answer running.
sudo zypper install firewalldsudo systemctl enable --now firewalldAvailable in the official repositories, not installed by default. If UFW meets your needs, stick with UFW. If you need the zone model:
sudo apt updatesudo apt install -y firewalldsudo systemctl enable --now firewalldDisable UFW first to avoid any conflict:
sudo systemctl disable --now ufwsudo pacman -S firewalldsudo systemctl enable --now firewalldAllow SSH before anything else
This is the step never to skip. You must know the active zone of the administration interface before allowing SSH, otherwise you open the port in the wrong zone and cause a lockout at the first disconnection.
Find the active zone:
sudo firewall-cmd --get-active-zones# Typical output:# public# interfaces: eth0# internal# interfaces: eth1The interface carrying your SSH session (often eth0) is attached to a
zone, here public. It is in that very zone that SSH must be allowed. For
the commands of this section, replace public with your active zone.
Check that ssh is already in the zone (it is by default on RHEL/Fedora):
sudo firewall-cmd --zone=public --list-servicesIf ssh does not appear in the output:
# runtimesudo firewall-cmd --zone=public --add-service=ssh
# permanentsudo firewall-cmd --zone=public --add-service=ssh --permanentsudo firewall-cmd --reloadIf SSH listens on a non-standard port, open the raw port:
sudo firewall-cmd --zone=public --add-port=2222/tcp --permanentsudo firewall-cmd --reloadZones: read, assign, switch
Once SSH is secured, the commands below are used to inspect zones, attach an interface to one and create one for a dedicated segment.
List all available zones:
sudo firewall-cmd --get-zonesShow the details of a zone (services, ports, sources, interfaces):
sudo firewall-cmd --zone=public --list-allAttach an interface to a zone (persistent):
sudo firewall-cmd --zone=internal --change-interface=eth1 --permanentsudo firewall-cmd --reloadSet the default zone:
sudo firewall-cmd --set-default-zone=publicCreate a custom zone, useful for a dedicated network segment:
sudo firewall-cmd --new-zone=administration --permanentsudo firewall-cmd --reloadsudo firewall-cmd --zone=administration --add-source=10.10.10.0/24 --permanentsudo firewall-cmd --zone=administration --add-service=ssh --permanentsudo firewall-cmd --reloadOpen services and ports
firewalld ships more than 260 preconfigured services (263 counted on
AlmaLinux 10), as XML files in /usr/lib/firewalld/services/. List them:
sudo firewall-cmd --get-services | tr ' ' '\n' | headAllow a service
A firewalld service is a named set of ports and protocols, defined in
/usr/lib/firewalld/services/. Opening it by name saves you from knowing its
port numbers.
# runtimesudo firewall-cmd --zone=public --add-service=https
# permanent + reloadsudo firewall-cmd --zone=public --add-service=https --permanentsudo firewall-cmd --reloadOpen a raw port
Useful when no predefined service matches; the protocol must be specified, tcp is not implicit.
sudo firewall-cmd --zone=public --add-port=8080/tcp --permanentsudo firewall-cmd --reloadRemove
Removal follows the same syntax as adding, with --remove-: the rule must match exactly the one that was set.
sudo firewall-cmd --zone=public --remove-service=https --permanentsudo firewall-cmd --zone=public --remove-port=8080/tcp --permanentsudo firewall-cmd --reloadList what is open
The output describes the permanent configuration or the current one, depending on whether --permanent is present: the two can differ.
sudo firewall-cmd --zone=public --list-servicessudo firewall-cmd --zone=public --list-portssudo firewall-cmd --zone=public --list-allRich rules: when services are not enough
Rich rules (--add-rich-rule) express criteria that simple services do
not cover: a precise source, an advanced action, rate limiting,
per-rule logging. Syntax:
# Allow HTTP only from an internal subnetsudo firewall-cmd --zone=public --add-rich-rule=' rule family="ipv4" source address="192.168.1.0/24" service name="http" accept' --permanent
# Explicitly block a hammering IPsudo firewall-cmd --zone=public --add-rich-rule=' rule family="ipv4" source address="203.0.113.42" reject' --permanent
# Rate-limit new SSH connectionssudo firewall-cmd --zone=public --add-rich-rule=' rule service name="ssh" accept limit value="6/m"' --permanent
sudo firewall-cmd --reloadList the active rich rules:
sudo firewall-cmd --zone=public --list-rich-rulesTo delete one, replace --add-rich-rule with --remove-rich-rule. firewalld
parses the rule before comparing it: whitespace and attribute order do
not matter, but every element and every value must match. A rule that is not
found only produces a NOT_ENABLED warning with a return code of 0: run
--list-rich-rules again to confirm the removal.
Logging and troubleshooting
Refused packets leave no trace by default. Enable logging of refused packets:
# values: off | all | unicast | broadcast | multicastsudo firewall-cmd --set-log-denied=unicastThe unicast value is enough for troubleshooting without flooding the
log. These messages are emitted by the kernel, not by the daemon: they
carry prefixes such as FINAL_REJECT: and are read in the kernel log, not
in the log of the firewalld unit.
sudo journalctl -k --since "10 min ago" | grep -E 'REJECT|DROP'# or livesudo dmesg --follow | grep -i rejectFull state of the service:
sudo systemctl status firewalldsudo firewall-cmd --statesudo firewall-cmd --list-all-zonesBest practices
- Always test in runtime before saving with
--permanent. - Attach interfaces to an explicit zone rather than relying on the default zone: it avoids surprises after adding a new network card.
- Prefer services over raw ports: maintenance becomes readable (
--add-service=httprather than--add-port=80/tcp). - Create a custom service if you expose an application on a non-standard port: XML file under
/etc/firewalld/services/<name>.xml,firewall-cmd --reload, then--add-service=<name>. - Log (
--set-log-denied=unicast) while opening a service, then disable it to keep the log clean. - Back up
/etc/firewalld/: it holds all your permanent customisations (XML files, can be versioned in Git). Do not confuse it with the effective state: see the warning below.
Security and rollback
When something goes wrong, three fallback levels exist, from the gentlest to the most drastic. The first two are almost always enough; the third, the panic mode, belongs to incident response.
# 1. Go back to the last loaded permanent configuration (cancels runtime changes)sudo firewall-cmd --reload
# 2. Remove a runtime rule immediatelysudo firewall-cmd --zone=public --remove-service=<service>sudo firewall-cmd --zone=public --remove-rich-rule='<exact rule>'
# 3. Panic mode: blocks ALL traffic, inbound AND outboundsudo firewall-cmd --panic-on# once the incident is under control from the fallback console:sudo firewall-cmd --panic-offFor a full rollback of the permanent configuration, restore
/etc/firewalld/ from your backup (or delete the modified file to fall
back on the definition in /usr/lib/firewalld/, the one of the
distribution), then run firewall-cmd --reload.
Quick troubleshooting
The table starts from the symptom you observe, gives the most likely cause, then the action that fixes it.
| Symptom | Probable cause | Action |
|---|---|---|
SSH cut after --reload | ssh service removed from the active zone, or default zone changed | Console → firewall-cmd --zone=<active> --add-service=ssh then --permanent |
| Rule visible but inactive | Added with --permanent without --reload | firewall-cmd --reload |
| Rule gone after reboot | Added in runtime only | Replay with --permanent |
| Port open but service unreachable | Service listens on 127.0.0.1 or wrong zone assigned to the interface | ss -tulpn on the server, firewall-cmd --get-active-zones |
| Packets blocked without a trace | log-denied disabled (default) | firewall-cmd --set-log-denied=unicast, then journalctl -k |
| Conflict with UFW or manual iptables | Two managers active at the same time | systemctl disable --now ufw; do not run direct iptables rules |
rich-rule still present after --remove-rich-rule | An element or a value differs from the rule that was set (just a NOT_ENABLED warning) | Copy the rule from --list-rich-rules |
limit value has no effect on SSH | The zone's ssh service accepts before the rich rule | Remove the ssh service once the rich rule is in place |
Put it into practice
The runtime vs permanent pitfall is easier to understand by living it: this lab asks you to open a service in the right zone, make the rule permanent, then prove that it survives a reload of firewalld.
Key points
- firewalld attaches interfaces to zones; each zone carries its own services / ports / rich rules.
- Runtime ≠ permanent. Without
--permanent, the rule is lost at the next--reloador reboot. firewall-cmd --reloadreplays the permanent configuration; it does not interrupt established TCP connections.- Always allow SSH explicitly in the active zone of the admin interface before any deep change.
- For most needs,
--add-service=<name>is more readable than--add-port=<n>/tcp. - Rich rules for anything that needs a source, rate limiting or per-rule logging.
--set-log-denied=unicastis the number one troubleshooting tool, and its traces are in the kernel log.--panic-onexists for incidents; never in normal configuration.- On Debian/Ubuntu,
UFWis almost always enough; firewalld on Red Hat, full stop.
Knowledge check
Check that the essentials of this guide are acquired. The questions only cover what has just been explained here.
Knowledge check
Check what you have learned with this interactive quiz
How it works
- The timer starts when you click Start
- Multiple choice, true/false and short answer questions
- You can move between questions
- Detailed results are shown at the end
Starts the quiz and the timer
Review
(0/0)Score by domain
A domain groups several guides and gets several questions, so its score is more reliable than a per-topic score, which often rests on a single question.
Summary
Strengths
No topic passed at 80% on at least two questions yet.
Weak areas
No topic below the pass mark.
Recommended next
Hints before you try again?
Continue the course: Configure a firewall with nftables
A new full quiz with random questions
Work again only on the questions you missed