Skip to content
Français
Account: not signed in, sync available My learning
Select theme
Français

Configure a firewall with firewalld

By updated

Improve this page

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 sudo rights
  • 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:

ZoneTypical useDefault policy
dropHostile (unknown network, heavy scanning)Inbound blocked without notification
blockHostile with explicit rejectionInbound blocked, ICMP reject
publicInternet, public Wi-Fi, frequent defaultssh, dhcpv6-client, cockpit allowed, the rest blocked
externalInternet egress with masquerade (NAT)ssh allowed, masquerade enabled
workOffice LANssh, dhcpv6-client, cockpit
homeHome LANssh, mdns, samba-client, dhcpv6-client, cockpit
internalTrusted LANSame as home
trustedEverything allowedNo 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:

Terminal window
sudo firewall-cmd --get-default-zone
sudo firewall-cmd --get-active-zones

Runtime 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 reload or reboot.
  • Permanent: the rules saved in /etc/firewalld/. Applied after reload or reboot.

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:

Terminal window
sudo dnf install -y firewalld
sudo systemctl enable --now firewalld

Check:

Terminal window
sudo firewall-cmd --version
sudo firewall-cmd --state

firewall-cmd --state must answer running.

Allow 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:

Terminal window
sudo firewall-cmd --get-active-zones
# Typical output:
# public
# interfaces: eth0
# internal
# interfaces: eth1

The 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):

Terminal window
sudo firewall-cmd --zone=public --list-services

If ssh does not appear in the output:

Terminal window
# runtime
sudo firewall-cmd --zone=public --add-service=ssh
# permanent
sudo firewall-cmd --zone=public --add-service=ssh --permanent
sudo firewall-cmd --reload

If SSH listens on a non-standard port, open the raw port:

Terminal window
sudo firewall-cmd --zone=public --add-port=2222/tcp --permanent
sudo firewall-cmd --reload

Zones: 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:

Terminal window
sudo firewall-cmd --get-zones

Show the details of a zone (services, ports, sources, interfaces):

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

Attach an interface to a zone (persistent):

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

Set the default zone:

Terminal window
sudo firewall-cmd --set-default-zone=public

Create a custom zone, useful for a dedicated network segment:

Terminal window
sudo firewall-cmd --new-zone=administration --permanent
sudo firewall-cmd --reload
sudo firewall-cmd --zone=administration --add-source=10.10.10.0/24 --permanent
sudo firewall-cmd --zone=administration --add-service=ssh --permanent
sudo firewall-cmd --reload

Open 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:

Terminal window
sudo firewall-cmd --get-services | tr ' ' '\n' | head

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

Terminal window
# runtime
sudo firewall-cmd --zone=public --add-service=https
# permanent + reload
sudo firewall-cmd --zone=public --add-service=https --permanent
sudo firewall-cmd --reload

Open a raw port

Useful when no predefined service matches; the protocol must be specified, tcp is not implicit.

Terminal window
sudo firewall-cmd --zone=public --add-port=8080/tcp --permanent
sudo firewall-cmd --reload

Remove

Removal follows the same syntax as adding, with --remove-: the rule must match exactly the one that was set.

Terminal window
sudo firewall-cmd --zone=public --remove-service=https --permanent
sudo firewall-cmd --zone=public --remove-port=8080/tcp --permanent
sudo firewall-cmd --reload

List what is open

The output describes the permanent configuration or the current one, depending on whether --permanent is present: the two can differ.

Terminal window
sudo firewall-cmd --zone=public --list-services
sudo firewall-cmd --zone=public --list-ports
sudo firewall-cmd --zone=public --list-all

Rich 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:

Terminal window
# Allow HTTP only from an internal subnet
sudo 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 IP
sudo firewall-cmd --zone=public --add-rich-rule='
rule family="ipv4"
source address="203.0.113.42"
reject
' --permanent
# Rate-limit new SSH connections
sudo firewall-cmd --zone=public --add-rich-rule='
rule service name="ssh"
accept
limit value="6/m"
' --permanent
sudo firewall-cmd --reload

List the active rich rules:

Terminal window
sudo firewall-cmd --zone=public --list-rich-rules

To 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:

Terminal window
# values: off | all | unicast | broadcast | multicast
sudo firewall-cmd --set-log-denied=unicast

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

Terminal window
sudo journalctl -k --since "10 min ago" | grep -E 'REJECT|DROP'
# or live
sudo dmesg --follow | grep -i reject

Full state of the service:

Terminal window
sudo systemctl status firewalld
sudo firewall-cmd --state
sudo firewall-cmd --list-all-zones

Best 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=http rather 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.

Terminal window
# 1. Go back to the last loaded permanent configuration (cancels runtime changes)
sudo firewall-cmd --reload
# 2. Remove a runtime rule immediately
sudo 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 outbound
sudo firewall-cmd --panic-on
# once the incident is under control from the fallback console:
sudo firewall-cmd --panic-off

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

SymptomProbable causeAction
SSH cut after --reloadssh service removed from the active zone, or default zone changedConsole → firewall-cmd --zone=<active> --add-service=ssh then --permanent
Rule visible but inactiveAdded with --permanent without --reloadfirewall-cmd --reload
Rule gone after rebootAdded in runtime onlyReplay with --permanent
Port open but service unreachableService listens on 127.0.0.1 or wrong zone assigned to the interfacess -tulpn on the server, firewall-cmd --get-active-zones
Packets blocked without a tracelog-denied disabled (default)firewall-cmd --set-log-denied=unicast, then journalctl -k
Conflict with UFW or manual iptablesTwo managers active at the same timesystemctl disable --now ufw; do not run direct iptables rules
rich-rule still present after --remove-rich-ruleAn 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 SSHThe zone's ssh service accepts before the rich ruleRemove 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 --reload or reboot.
  • firewall-cmd --reload replays 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=unicast is the number one troubleshooting tool, and its traces are in the kernel log.
  • --panic-on exists for incidents; never in normal configuration.
  • On Debian/Ubuntu, UFW is 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

6 questions
6 min
70% to pass

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

Is this project useful to you?

The guides, courses, quizzes and labs stay free. If you use them regularly, you can contribute to their upkeep and development.

Become a supporting member (new tab) One-time support (new tab) Learn more

Follow the project on LinkedIn