🎉 NEW YEAR SALE! 40% OFF on Annual Premium+ Plan - Till 31st Dec! Use SUPERSALE40 Shop Now →

Complete Guide to Firewalld Zones and Rules in RHEL 10

Published On: 17 April 2026

Objective 

Firewalld has been the default firewall manager on RHEL since version 7. That's nearly a decade. And yet it still regularly catches people off guardusually because they understand iptables well enough to be dangerous but haven't fully internalized the zone-based model that firewalld uses underneath. The concepts aren't complicated once they click. A zone is a trust level. Network interfaces and source addresses get assigned to zones. Rules belong to zones. Traffic hitting an interface gets evaluated against that zone's rules. That's basically it. Getting from that description to confidently managing a production RHEL 10 firewall is what this guide covers.

How Firewalld Is Structured

Firewalld sits on top of nftables (the kernel's packet filtering framework, replacing iptables in RHEL 8 and later). You interact with firewalld through firewall-cmd or the graphical tool firewall-config, and firewalld translates your rules into nftables rules automatically. You don't write nftables rules directly unless you have a very specific reason to. The key architectural piece to understand is the runtime versus permanent configuration split. Every change you make with firewall-cmd is by default applied only to the running configuration. It takes effect immediately but doesn't survive a reboot or a firewalld reload. To make something permanent you add permanent to the command. To apply permanent changes to the running configuration without rebooting, you reload:

sudo firewall-cmd reload

This distinction matters more than people expect. It's how you accidentally lock yourself out of a server after a rebootyou made a runtime change, it worked, you moved on, and then three weeks later the server reboots and the rule is gone. The habit to build is: always use permanent for anything you want to keep, then reload.

Zones: What They Are and What Each One Means

Firewalld ships with nine predefined zones. Each one represents a different level of trust in the network connected to it. You can create custom zones, but for most environments the predefined ones cover everything you need.

 

Zone Trust Level Typical Use Case
drop None All incoming traffic silently dropped, no response sent
block None Incoming connections rejected with an ICMP message
public Low Default zone; internet-facing interfaces
external Low External interface on a router; masquerading enabled
dmz Medium-Low Publicly accessible servers with limited internal access
work Medium Office or corporate network environments
home Medium-High Home networks where you trust most machines
internal High Internal network; similar to home
trusted Full All connections accepted; use very carefully

 

  • The public zone is the default for any interface not explicitly assigned elsewhere. On a fresh RHEL 10 install, your primary network interface is almost certainly in the public zone. That's a reasonable default for a server with an internet-facing interfaceit only allows SSH and DHCPv6 client traffic by default and drops everything else.
  • The trusted zone accepts all traffic without restriction. Assigning an interface to trusted is equivalent to turning the firewall off for that interface. There are valid use cases for thisloopback interfaces, tightly controlled internal management networksbut it's the zone most likely to create a security problem if assigned carelessly.

Checking Your Current Configuration

Before changing anything, get a clear picture of what's running. These are the commands you'll use constantly:

# Check firewalld is running
sudo systemctl status firewalld

# See the default zone
sudo firewall-cmd get-default-zone

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

# List active zones and which interfaces are assigned to them
sudo firewall-cmd get-active-zones

# Full details of the default zone
sudo firewall-cmd list-all

# Full details of a specific zone
sudo firewall-cmd zone=public list-all

# Full details of every zone at once
sudo firewall-cmd list-all-zones

The output of list-all shows you services, ports, protocols, sources, and rich rules assigned to that zone. Get comfortable reading this outputit's your source of truth for what the firewall is actually doing.

Assigning Interfaces and Sources to Zones

Two things can be assigned to a zone: network interfaces and source IP addresses or ranges. Interface assignment is the coarser controlall traffic arriving on that interface gets evaluated against the zone's rules. Source assignment is more granulartraffic from a specific IP or subnet goes to a specific zone regardless of which interface it arrived on. Source assignments take priority over interface assignments when both match.

# Assign an interface to a zone permanently
sudo firewall-cmd permanent zone=internal change-interface=eth1

# Assign a source IP range to a zone
sudo firewall-cmd permanent zone=trusted add-source=192.168.100.0/24

# Remove a source from a zone
sudo firewall-cmd permanent zone=trusted remove-source=192.168.100.0/24

# Check which zone an interface is in
sudo firewall-cmd get-zone-of-interface=eth0

A practical example: your server has two interfaces, eth0 facing the internet and eth1 on an internal management network. You'd put eth0 in the public zone and eth1 in the internal zone. The rules for each zone would be very differentminimal services allowed on public, broader access allowed on internal.

Working With Services

Firewalld ships with predefined service definitions for common applicationsSSH, HTTP, HTTPS, DNS, NFS, and dozens of others. A service definition bundles together the ports and protocols that application needs. Using service names instead of raw port numbers makes your configuration more readable and easier to audit.

# See all predefined services
sudo firewall-cmd get-services

# Details about a specific service definition
sudo firewall-cmd info-service=http

# Allow HTTP in the public zone permanently
sudo firewall-cmd permanent zone=public add-service=http

# Allow both HTTP and HTTPS
sudo firewall-cmd permanent zone=public add-service=http add-service=https

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

# Apply changes to running config
sudo firewall-cmd reload

Creating a Custom Service Definition

When you're running an application on a non-standard port, you can either add the raw port directly or create a custom service definition. The service definition approach is better for anything you'll manage regularlyit gives you a named object you can add and remove cleanly.

# Create a new service definition file
sudo firewall-cmd new-service=myapp permanent

# Add ports to the new service
sudo firewall-cmd permanent service=myapp add-port=8443/tcp
sudo firewall-cmd permanent service=myapp add-port=9090/tcp

# Optional: add a description
sudo firewall-cmd permanent service=myapp set-description="My Application Service"

# Now add the service to a zone
sudo firewall-cmd permanent zone=public add-service=myapp
sudo firewall-cmd reload

Opening and Closing Ports Directly

For quick one-off rules where creating a service definition is overkill, you can add ports directly to a zone:

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

# Open a UDP port
sudo firewall-cmd permanent zone=public add-port=5353/udp

# Open a range of ports
sudo firewall-cmd permanent zone=public add-port=8000-8100/tcp

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

sudo firewall-cmd reload

Rich Rules: When You Need Finer Control

Basic zone rules apply uniformly to all traffic hitting that zone. Rich rules let you write conditions based on source address, destination address, port, protocol, and actionall in one rule. This is where firewalld starts to feel like a real firewall policy tool.

The rich rule syntax follows a consistent pattern:

rule [family="ipv4|ipv6"]
  [source address="address/mask"]
  [destination address="address/mask"]
  [service name="service-name"] | [port port="port" protocol="tcp|udp"]
  [log [prefix="prefix"] [level="level"] [limit value="rate/duration"]]
  [accept|reject|drop]

Common Rich Rule Examples

# Allow SSH only from a specific IP address
sudo firewall-cmd permanent zone=public \
  add-rich-rule='rule family="ipv4" source address="203.0.113.10/32" service name="ssh" accept'

# Block all traffic from a specific subnet
sudo firewall-cmd permanent zone=public \
  add-rich-rule='rule family="ipv4" source address="10.0.5.0/24" drop'

# Allow HTTPS from a subnet with logging
sudo firewall-cmd permanent zone=public \
  add-rich-rule='rule family="ipv4" source address="192.168.1.0/24" service name="https" log prefix="HTTPS_ALLOW" level="info" accept'

# Rate-limit SSH connections to prevent brute force
sudo firewall-cmd permanent zone=public \
  add-rich-rule='rule family="ipv4" service name="ssh" limit value="5/m" accept'

# Reject traffic from a specific IP with a polite response
sudo firewall-cmd permanent zone=public \
  add-rich-rule='rule family="ipv4" source address="198.51.100.0/24" reject'

# Allow a specific port only from an internal range
sudo firewall-cmd permanent zone=public \
  add-rich-rule='rule family="ipv4" source address="10.0.0.0/8" port port="5432" protocol="tcp" accept'
# List all rich rules in a zone
sudo firewall-cmd zone=public list-rich-rules

# Remove a rich rule (exact syntax must match what was added)
sudo firewall-cmd permanent zone=public \
  remove-rich-rule='rule family="ipv4" source address="10.0.5.0/24" drop'

sudo firewall-cmd reload

The rate-limiting rich rule is worth calling out. Limiting SSH to 5 connections per minute doesn't stop a determined attacker but it does eliminate the noise of automated brute-force scanners and reduces the load of failed attempts in your auth logs. It's a low-effort, high-value rule for any public-facing SSH port.

IP Masquerading and Port Forwarding

If your RHEL 10 server is acting as a router or gatewaytraffic from an internal network going out through the server to the internetyou need masquerading. Masquerading is NAT: the server rewrites the source address of outgoing packets to its own IP so return traffic knows where to come back to.

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

# Verify it's enabled
sudo firewall-cmd zone=external query-masquerade

# Disable if needed
sudo firewall-cmd permanent zone=external remove-masquerade

Port forwarding redirects traffic arriving on one port to a different port or a different host entirely. Common use case: a single public IP needs to route web traffic to different internal servers.

# Forward port 8080 on this machine to port 80 on an internal host
sudo firewall-cmd permanent zone=public \
  add-forward-port=port=8080:proto=tcp:toaddr=192.168.1.50:toport=80

# Forward port 2222 on this machine to port 22 on another host
sudo firewall-cmd permanent zone=public \
  add-forward-port=port=2222:proto=tcp:toaddr=192.168.1.30:toport=22

# List forwarded ports
sudo firewall-cmd zone=public list-forward-ports

sudo firewall-cmd reload

Managing the Default Zone

The default zone is where traffic goes when the arriving interface hasn't been explicitly assigned to a zone. Changing the default zone affects all unassigned interfaces:

# Check the current default zone
sudo firewall-cmd get-default-zone

# Change the default zone
sudo firewall-cmd set-default-zone=dmz

# Verify
sudo firewall-cmd get-default-zone

On a server that only has one network interface and it's internet-facing, leaving the default as public is the right call. On a more complex setup with multiple interfaces serving different roles, think carefully about which zone should be the fallback for anything that doesn't have an explicit assignment.

Creating and Managing Custom Zones

The predefined zones cover most scenarios, but there are cases where a custom zone with a specific name and policy makes the configuration cleaner and easier to reason about.

# Create a new zone
sudo firewall-cmd permanent new-zone=monitoring

# Add services to the new zone
sudo firewall-cmd permanent zone=monitoring add-service=http
sudo firewall-cmd permanent zone=monitoring add-port=9090/tcp
sudo firewall-cmd permanent zone=monitoring add-port=9100/tcp

# Assign a source range to the zone
sudo firewall-cmd permanent zone=monitoring add-source=10.0.10.0/24

# Reload to activate
sudo firewall-cmd reload

# Verify
sudo firewall-cmd zone=monitoring list-all

Custom zones live in /etc/firewalld/zones/ as XML files. You can edit them directly if you're comfortable with the XML format, though firewall-cmd is safer for most changes.

Panic Mode and Emergency Lockdown

If you suspect active network intrusion or need to immediately cut all network traffic, firewalld has a panic mode that drops all incoming and outgoing traffic instantly:

# Enable panic modedrops ALL network traffic immediately
sudo firewall-cmd panic-on

# Check if panic mode is active
sudo firewall-cmd query-panic

# Disable panic mode and restore normal operation
sudo firewall-cmd panic-off

Panic mode is a blunt instrument. It will terminate your SSH session if you're connected remotely. Only use this when you have console access or an out-of-band management connection and you need to stop all traffic right now. Disabling panic mode restores all previous rulesit doesn't modify your configuration.

Logging Firewall Activity

By default, firewalld doesn't log dropped or rejected packets. For a production environment where you need to audit what's being blocked or investigate connection issues, you want some level of logging.

# Set the firewall log denied traffic level
# Options: off, unicast, broadcast, multicast, all
sudo firewall-cmd set-log-denied=all

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

# Logs appear in the kernel logview with:
sudo journalctl -k | grep -i "FINAL_REJECT\|DROPped"
# or
sudo dmesg | grep -i reject

Logging everything with all can be noisy on a server that sees a lot of traffic. unicast is a more practical default for most serversit logs denied unicast (directed) traffic without flooding the logs with broadcast noise.

For more targeted logging, use rich rules with a log action on specific ports or sources you care about rather than enabling global log-denied:

# Log and accept SSH from a specific network
sudo firewall-cmd permanent zone=public \
  add-rich-rule='rule family="ipv4" source address="10.0.0.0/8" service name="ssh" log prefix="SSH_INTERNAL" level="notice" limit value="3/m" accept'

Saving, Restoring, and Backing Up Firewall Rules

Permanent firewalld configuration lives in XML files under /etc/firewalld/. Backing up your firewall config is as simple as copying that directory:

# Back up firewall configuration
sudo cp -r /etc/firewalld/ /root/firewalld-backup-$(date +%F)

# View permanent zone configurations
ls /etc/firewalld/zones/
cat /etc/firewalld/zones/public.xml

To restore from a backup, copy the XML files back and reload:

sudo cp -r /root/firewalld-backup-2026-03-01/* /etc/firewalld/
sudo firewall-cmd complete-reload

The complete-reload flag restarts firewalld entirely rather than just reloading rules, which is more thorough for a full configuration restore.

Troubleshooting Common Firewalld Problems

A rule isn't working after a reboot

You made a runtime change without permanent. Check the permanent configuration with list-all and compare it to what you expected. Re-add the rule with permanent and reload.

# Compare runtime vs permanent config
sudo firewall-cmd zone=public list-all
sudo firewall-cmd zone=public list-all permanent

A service is allowed but still not reachable

Check whether the application is actually listening on the port you opened. A firewall rule allowing port 8080 does nothing if nothing is bound to port 8080:

# Check what's listening on which ports
sudo ss -tlnp

# Or for a specific port
sudo ss -tlnp | grep :8080

Also check SELinux if you're on RHEL 10SELinux can block a network service from binding to a port even if the firewall allows it:

sudo ausearch -m avc -ts recent | grep denied

The wrong interface is in the wrong zone

# See all interface-to-zone mappings
sudo firewall-cmd get-active-zones

# Move an interface to the correct zone
sudo firewall-cmd permanent zone=internal change-interface=eth1
sudo firewall-cmd reload

Firewalld and nftables conflicts

If you've been manually writing nftables rules alongside firewalld, conflicts can occur. Firewalld manages its own nftables tablesmixing direct nftables rules with firewalld management is possible but requires care. Check what nftables has currently:

sudo nft list ruleset

A Production Configuration Checklist

  • Always use permanent for anything you want to keepthen reload to apply. Make this a reflex.
  • Keep SSH in the correct zoneif you lock yourself out by moving SSH to a zone without an SSH rule, you'll need console access to fix it.
  • Restrict SSH by source IP where possiblea rich rule limiting SSH to known management IPs dramatically reduces exposure.
  • Audit active zones regularlyrun list-all-zones and make sure nothing is assigned to the wrong zone, especially trusted.
  • Back up /etc/firewalld/ before major changesthe XML files are easy to restore from and a backup takes seconds.
  • Test on non-production firsta misconfigured firewall rule on a production server is a bad time. Verify behavior in a test environment.
  • Use service definitions instead of raw portsnamed services make configurations easier to read, audit, and share across systems.
  • Enable logging on denied trafficeven at the unicast level, this gives you visibility into what's being blocked without flooding logs.

Conclusion

Firewalld's zone model is genuinely a cleaner way to manage firewall policy than writing raw iptables or nftables rules by hand. The learning curve is mostly just internalizing the runtime versus permanent distinction and understanding which zone each interface belongs to. Once those two things are second nature, the rest follows logically. RHEL 10 ships firewalld in good shape with nftables as the backend, stronger default crypto policies, and SELinux integration that works alongside the firewall rather than fighting it. Get comfortable with firewall-cmd list-all as your regular audit command, build the habit of always adding permanent before you reload, and the firewall stops being something that surprises you and starts being something you trust.