RED HAT ENTERPRISE LINUX
Restrict Network Access
with firewalld
Restrict network access using firewalld and firewall-cmd
CIS126RH | RHEL System Administration 1
Mesa Community College
firewalld is the default firewall management service on RHEL 9. It uses a
zone-based model — each network interface is assigned to a zone
that defines which traffic is allowed. The firewall-cmd command-line
tool controls firewalld at runtime and persistently. This module covers adding and
removing services and ports, understanding zones, and making changes permanent.
firewalld configuration is tested extensively on the RHCSA exam.
Learning Objectives
- Explain the firewalld zone model — Describe how zones work, name the built-in zones, and explain how network interfaces are assigned to zones
-
Allow and block services and ports —
Use
firewall-cmdto add and remove services and port/protocol rules both at runtime and persistently - Manage zones and interface assignments — Assign interfaces to zones, change the default zone, and list zone configurations
-
Verify and troubleshoot firewall rules —
Use
firewall-cmd --list-all,--query-service, and related commands to confirm the firewall state
How firewalld Works
firewalld is a front-end for the kernel's netfilter framework. It organises rules into zones — named trust levels applied to network connections.
- Every network interface is assigned to exactly one zone
- A zone defines what inbound traffic is accepted by default — everything not explicitly allowed is dropped
- Changes can be runtime (immediate, lost on reload/reboot) or permanent (written to disk, survive reboot)
- firewalld uses nftables as the kernel backend on RHEL 9
- firewalld runs as the
firewalld.servicesystemd service
Without --permanent, a rule is active now but lost after
firewall-cmd --reload or a reboot. To make rules persistent,
use --permanent and then firewall-cmd --reload
to activate them in the current session.
firewalld Zones
Zones represent trust levels for network connections. The zone applied to an interface determines which inbound traffic is accepted.
| Zone | Default trust level | Typical use |
|---|---|---|
drop | Lowest — all inbound dropped, no response | Maximum security; complete traffic blocking |
block | All inbound rejected with ICMP error | Blocks traffic but sends rejection message |
public | Distrust; only selected services allowed | Default zone; untrusted networks (internet) |
external | External networks with masquerading | Router/gateway internet-facing interface |
dmz | Limited access; public-facing servers | DMZ servers with controlled inbound access |
work | Mostly trusted; coworker machines | Corporate network internal interfaces |
home | Mostly trusted; home network | Home network; more services allowed by default |
internal | High trust; internal network | Internal server network |
trusted | Highest — all connections accepted | Fully trusted networks; accept everything |
Checking firewalld Status
# Confirm firewalld is running
$ systemctl status firewalld
● firewalld.service - firewalld - dynamic firewall daemon
Active: active (running) ...
# Check the firewall state (running/not running)
$ sudo firewall-cmd --state
running
# Show the default zone
$ sudo firewall-cmd --get-default-zone
public
# Show all active zones and their interfaces
$ sudo firewall-cmd --get-active-zones
public
interfaces: ens3
# Show everything allowed in the default zone
$ sudo firewall-cmd --list-all
public (active)
target: default
icmp-block-inversion: no
interfaces: ens3
sources:
services: cockpit dhcpv6-client ssh
ports:
protocols:
masquerade: no
forward-ports:
rich rules:
Adding and Removing Services
firewalld uses service definitions — named rules that describe the ports and protocols for common applications. Services are simpler than raw port rules.
# Add a service to the default zone (runtime only)
$ sudo firewall-cmd --add-service=http
# Add a service permanently (survives reload and reboot)
$ sudo firewall-cmd --permanent --add-service=http
# Reload to apply permanent changes to the running configuration
$ sudo firewall-cmd --reload
# Full permanent + reload workflow (recommended)
$ sudo firewall-cmd --permanent --add-service=http
$ sudo firewall-cmd --permanent --add-service=https
$ sudo firewall-cmd --reload
# Remove a service
$ sudo firewall-cmd --permanent --remove-service=http
$ sudo firewall-cmd --reload
# Verify a service is allowed
$ sudo firewall-cmd --query-service=http
yes
Always use --permanent for exam tasks (the grader reboots).
After adding all services, run firewall-cmd --reload once to
activate them in the current session.
Common Service Names
firewalld knows about hundreds of pre-defined services. Each service definition specifies the ports and protocols for that application.
| Service name | Ports | Application |
|---|---|---|
ssh | 22/tcp | Secure Shell remote access |
http | 80/tcp | Apache/nginx web server |
https | 443/tcp | Web server with TLS |
ftp | 21/tcp | File Transfer Protocol |
dns | 53/tcp, 53/udp | DNS server (BIND/named) |
smtp | 25/tcp | Mail server (outbound) |
nfs | 2049/tcp, 2049/udp | NFS server |
ntp | 123/udp | Network Time Protocol |
samba | 135/tcp, 137-138/udp, 139/tcp, 445/tcp | Windows file sharing |
cockpit | 9090/tcp | RHEL web console |
# List all available service definitions
$ sudo firewall-cmd --get-services
# View the ports defined for a specific service
$ sudo firewall-cmd --info-service=http
Adding and Removing Ports
When a service definition does not exist for a port, use
--add-port to open a specific port/protocol combination.
# Add a specific port/protocol (permanent)
$ sudo firewall-cmd --permanent --add-port=8080/tcp
$ sudo firewall-cmd --permanent --add-port=8443/tcp
# Add a port range
$ sudo firewall-cmd --permanent --add-port=8000-8100/tcp
# Add a UDP port
$ sudo firewall-cmd --permanent --add-port=514/udp
# Remove a port
$ sudo firewall-cmd --permanent --remove-port=8080/tcp
# Reload after permanent changes
$ sudo firewall-cmd --reload
# Verify a port is open
$ sudo firewall-cmd --query-port=8080/tcp
yes
# List all open ports in the current zone
$ sudo firewall-cmd --list-ports
8080/tcp 8443/tcp
Zones and Interface Assignment
Each interface belongs to exactly one zone. The default zone receives any interface not explicitly assigned elsewhere.
# Show the default zone
$ sudo firewall-cmd --get-default-zone
public
# Change the default zone to dmz
$ sudo firewall-cmd --set-default-zone=dmz
success
# Assign an interface to a specific zone (runtime)
$ sudo firewall-cmd --zone=internal --add-interface=ens4
# Assign an interface to a zone (permanent)
$ sudo firewall-cmd --permanent --zone=internal --add-interface=ens4
# Change an interface's zone
$ sudo firewall-cmd --permanent --zone=public --change-interface=ens3
# Show what zone an interface is in
$ sudo firewall-cmd --get-zone-of-interface=ens3
public
# List full configuration of a specific zone
$ sudo firewall-cmd --zone=public --list-all
Adding Services to a Specific Zone
Without a --zone= flag, all firewall-cmd operations
target the default zone. Use --zone= to target
a specific zone explicitly.
# Add http to the public zone (default — no --zone needed)
$ sudo firewall-cmd --permanent --add-service=http
# Add http to a specific named zone
$ sudo firewall-cmd --permanent --zone=dmz --add-service=http
# Add a port to the internal zone
$ sudo firewall-cmd --permanent --zone=internal --add-port=8080/tcp
# List configuration for the dmz zone
$ sudo firewall-cmd --zone=dmz --list-all
dmz
target: default
interfaces:
services: ssh http
ports:
# List all zones and their configuration
$ sudo firewall-cmd --list-all-zones
# Reload to apply all permanent changes
$ sudo firewall-cmd --reload
Rich Rules: Advanced Traffic Control
Rich rules provide more granular control than service or port rules — they can match source IP, destination, and apply logging or limiting.
# Allow SSH from a specific source IP only
$ sudo firewall-cmd --permanent \
--add-rich-rule='rule family="ipv4" source address="192.168.1.0/24" service name="ssh" accept'
# Block all traffic from a specific IP
$ sudo firewall-cmd --permanent \
--add-rich-rule='rule family="ipv4" source address="10.0.0.99" reject'
# Allow a port from a specific subnet with logging
$ sudo firewall-cmd --permanent \
--add-rich-rule='rule family="ipv4" source address="192.168.1.0/24" port port="8080" protocol="tcp" log prefix="web-access " accept'
# Reload and verify
$ sudo firewall-cmd --reload
$ sudo firewall-cmd --list-rich-rules
Rich rules are needed when you must restrict access by source IP address. For most RHCSA exam tasks, simple service or port rules are sufficient. Rich rules may appear in advanced scenarios.
Source-Based Zone Assignment
Instead of assigning by interface, a zone can be assigned by source IP address — traffic from a specific subnet goes to a specific zone.
# Add a source IP range to the trusted zone
$ sudo firewall-cmd --permanent \
--zone=trusted --add-source=192.168.1.0/24
# Add a single host to the trusted zone
$ sudo firewall-cmd --permanent \
--zone=trusted --add-source=10.0.0.5
# Add a source to the block zone (blocks all traffic from that IP)
$ sudo firewall-cmd --permanent \
--zone=block --add-source=203.0.113.50
# List sources in each zone
$ sudo firewall-cmd --zone=trusted --list-sources
192.168.1.0/24
$ sudo firewall-cmd --reload
Verifying Firewall Configuration
# Complete view of the active (running) default zone
$ sudo firewall-cmd --list-all
public (active)
target: default
interfaces: ens3
services: cockpit dhcpv6-client http https ssh
ports: 8080/tcp
rich rules:
# Check a specific service is allowed
$ sudo firewall-cmd --query-service=http
yes
# Check a specific port is open
$ sudo firewall-cmd --query-port=8080/tcp
yes
# Show only services in the default zone
$ sudo firewall-cmd --list-services
cockpit dhcpv6-client http https ssh
# Verify the permanent configuration (what survives reboot)
$ sudo firewall-cmd --permanent --list-all
# The running and permanent configs should match after --reload
# If they differ, --reload has not been run yet
Run firewall-cmd --list-all (running) and
firewall-cmd --permanent --list-all (permanent) to confirm
both match after --reload. The exam grader reboots and checks
the permanent configuration.
firewall-cmd Quick Reference
| Task | Command |
|---|---|
| Check firewalld is running | sudo firewall-cmd --state |
| Show default zone | sudo firewall-cmd --get-default-zone |
| Show active zones and interfaces | sudo firewall-cmd --get-active-zones |
| List all rules in default zone | sudo firewall-cmd --list-all |
| List permanent rules | sudo firewall-cmd --permanent --list-all |
| Add a service (permanent) | sudo firewall-cmd --permanent --add-service=SERVICE |
| Remove a service (permanent) | sudo firewall-cmd --permanent --remove-service=SERVICE |
| Add a port (permanent) | sudo firewall-cmd --permanent --add-port=PORT/PROTO |
| Remove a port (permanent) | sudo firewall-cmd --permanent --remove-port=PORT/PROTO |
| Apply permanent changes now | sudo firewall-cmd --reload |
| Query if a service is allowed | sudo firewall-cmd --query-service=SERVICE |
| Query if a port is open | sudo firewall-cmd --query-port=PORT/PROTO |
| Set the default zone | sudo firewall-cmd --set-default-zone=ZONE |
| List all available services | sudo firewall-cmd --get-services |
Common Mistakes
| Mistake | What goes wrong | Correct approach |
|---|---|---|
Adding a rule without --permanent |
Rule is active now but disappears on --reload or reboot — exam grading fails |
Always use --permanent then --reload |
Forgetting --reload after --permanent |
Rule is saved to disk but NOT active in the current session — service still blocked | Always run sudo firewall-cmd --reload after --permanent changes |
Using --restart instead of --reload |
Restarting firewalld (via systemctl restart) drops all runtime rules and connections | Use firewall-cmd --reload — applies permanent rules without dropping connections |
| Adding a service to the wrong zone | Rule exists in the DMZ zone but the interface is in the public zone — service still blocked | Always check which zone is active with --get-active-zones before adding rules |
| Disabling firewalld to "fix" a connectivity problem | All firewall protection removed — system is fully exposed | Diagnose and add the correct service/port rule instead of disabling the firewall |
| Confusing runtime and permanent listing commands | --list-all shows the running config; --permanent --list-all shows the saved config |
After --reload, both should match — run both to confirm |
Complete Configuration Example
The exam scenario: deploy an Apache web server and configure the firewall to allow HTTP and HTTPS traffic persistently.
# Step 1: Install and start Apache
$ sudo dnf install -y httpd
$ sudo systemctl enable --now httpd
# Step 2: Check what the firewall currently allows
$ sudo firewall-cmd --list-all
public (active)
services: cockpit dhcpv6-client ssh
# http and https are NOT currently allowed
# Step 3: Add http and https permanently
$ sudo firewall-cmd --permanent --add-service=http
success
$ sudo firewall-cmd --permanent --add-service=https
success
# Step 4: Apply permanent changes to the running config
$ sudo firewall-cmd --reload
success
# Step 5: Verify both services are now allowed
$ sudo firewall-cmd --list-all
public (active)
services: cockpit dhcpv6-client http https ssh
$ sudo firewall-cmd --query-service=http
yes
Knowledge Check
Answer these before moving to the next slide.
- What is the difference between a runtime firewall rule and a permanent firewall rule in firewalld?
- Write the commands to permanently allow the
nfsservice in the default zone and then activate the change immediately. - Write the commands to permanently open port
9000/tcpin the default zone and verify it is open. firewall-cmd --list-allshowshttpin the services list, but web browsers cannot connect to the server. You ranfirewall-cmd --permanent --add-service=httpearlier. What is the likely cause, and what is the fix?- Write the command to check which zone is active for interface
ens3. - An administrator ran
firewall-cmd --add-service=http(without --permanent). The server rebooted overnight. Is HTTP traffic now allowed? Why or why not?
Knowledge Check — Answers
- A runtime rule takes effect immediately but is lost when
firewall-cmd --reloadis run or the system reboots. A permanent rule is written to disk and survives reboots, but does not take effect in the current session until--reloadis run. For the exam, always use--permanentthen--reload. sudo firewall-cmd --permanent --add-service=nfs
sudo firewall-cmd --reload
Verify:sudo firewall-cmd --query-service=nfs→ should returnyes.sudo firewall-cmd --permanent --add-port=9000/tcp
sudo firewall-cmd --reload
sudo firewall-cmd --query-port=9000/tcp→ should returnyes.- The likely cause is that
firewall-cmd --reloadwas not run after the--permanentchange. The rule is saved in the permanent configuration but has not been applied to the running configuration — the running config still does not allow HTTP. Fix:sudo firewall-cmd --reload. sudo firewall-cmd --get-zone-of-interface=ens3
Also accept:sudo firewall-cmd --get-active-zoneswhich shows all interfaces and their zones.- No — HTTP traffic is not allowed after the reboot. The command
was run without
--permanent, so it only modified the runtime configuration. When the system rebooted, firewalld loaded its permanent configuration from disk, which does not include the HTTP rule. To fix, add it permanently:sudo firewall-cmd --permanent --add-service=http && sudo firewall-cmd --reload.
Key Takeaways
-
firewalld uses zones — the default zone is public.
Every interface is in a zone; rules are added to zones.
Check the active zone with
firewall-cmd --get-active-zones. Without--zone=, all commands target the default zone. -
Always use
--permanentfor exam tasks.firewall-cmd --permanent --add-service=SERVICEsaves the rule to disk.firewall-cmd --reloadapplies it to the running configuration. Both steps are required — permanent without reload is not active; runtime without permanent is not persistent. -
Use service names when possible; raw ports for custom applications.
--add-service=httpopens port 80/tcp by name.--add-port=8080/tcpopens a specific port directly. List available services withfirewall-cmd --get-services. -
Verify with
--list-alland--query-service.firewall-cmd --list-allshows the running configuration.firewall-cmd --permanent --list-allshows the saved configuration. After--reload, both must match for the configuration to be correct and persistent.
Graded Lab
- Run
sudo firewall-cmd --state,--get-default-zone, and--list-allto record the baseline firewall configuration. Confirm which services are currently allowed in the public zone. - Install
httpdand start it withsystemctl enable --now. Confirm from another terminal or browser that HTTP is blocked by the firewall (connection refused). Then addhttppermanently, reload, and confirm it is accessible. - Add port
8080/tcpto the default zone permanently. Reload and verify withfirewall-cmd --query-port=8080/tcp. Also verify withfirewall-cmd --list-allthat it appears in the ports section. - Deliberately add a service without
--permanent:firewall-cmd --add-service=ftp. Runfirewall-cmd --reloadand observe that ftp disappears from the running configuration. This demonstrates the consequence of omitting --permanent. - Remove
port 8080/tcppermanently with--remove-port=8080/tcp. Reload and verify the port is no longer listed. Runfirewall-cmd --permanent --list-allto confirm the permanent configuration no longer includes it. - Reboot the system. After reboot, run
firewall-cmd --list-allto confirm thathttpis still allowed (added with --permanent) and8080/tcpis gone (removed with --permanent). This is the definitive persistence check.
"Restrict network access using firewalld and firewall-cmd."
Always --permanent then --reload. Verify with
--list-all and --query-service. Reboot confirms persistence.