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

  1. Explain the firewalld zone model — Describe how zones work, name the built-in zones, and explain how network interfaces are assigned to zones
  2. Allow and block services and ports — Use firewall-cmd to add and remove services and port/protocol rules both at runtime and persistently
  3. Manage zones and interface assignments — Assign interfaces to zones, change the default zone, and list zone configurations
  4. 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.service systemd service
Runtime vs permanent: the critical distinction

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
dropLowest — all inbound dropped, no responseMaximum security; complete traffic blocking
blockAll inbound rejected with ICMP errorBlocks traffic but sends rejection message
publicDistrust; only selected services allowedDefault zone; untrusted networks (internet)
externalExternal networks with masqueradingRouter/gateway internet-facing interface
dmzLimited access; public-facing serversDMZ servers with controlled inbound access
workMostly trusted; coworker machinesCorporate network internal interfaces
homeMostly trusted; home networkHome network; more services allowed by default
internalHigh trust; internal networkInternal server network
trustedHighest — all connections acceptedFully 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
RHCSA pattern: --permanent then --reload

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
ssh22/tcpSecure Shell remote access
http80/tcpApache/nginx web server
https443/tcpWeb server with TLS
ftp21/tcpFile Transfer Protocol
dns53/tcp, 53/udpDNS server (BIND/named)
smtp25/tcpMail server (outbound)
nfs2049/tcp, 2049/udpNFS server
ntp123/udpNetwork Time Protocol
samba135/tcp, 137-138/udp, 139/tcp, 445/tcpWindows file sharing
cockpit9090/tcpRHEL 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 more powerful but more complex

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
Verify both running AND permanent configurations

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 runningsudo firewall-cmd --state
Show default zonesudo firewall-cmd --get-default-zone
Show active zones and interfacessudo firewall-cmd --get-active-zones
List all rules in default zonesudo firewall-cmd --list-all
List permanent rulessudo 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 nowsudo firewall-cmd --reload
Query if a service is allowedsudo firewall-cmd --query-service=SERVICE
Query if a port is opensudo firewall-cmd --query-port=PORT/PROTO
Set the default zonesudo firewall-cmd --set-default-zone=ZONE
List all available servicessudo 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.

  1. What is the difference between a runtime firewall rule and a permanent firewall rule in firewalld?
  2. Write the commands to permanently allow the nfs service in the default zone and then activate the change immediately.
  3. Write the commands to permanently open port 9000/tcp in the default zone and verify it is open.
  4. firewall-cmd --list-all shows http in the services list, but web browsers cannot connect to the server. You ran firewall-cmd --permanent --add-service=http earlier. What is the likely cause, and what is the fix?
  5. Write the command to check which zone is active for interface ens3.
  6. 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

  1. A runtime rule takes effect immediately but is lost when firewall-cmd --reload is 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 --reload is run. For the exam, always use --permanent then --reload.
  2. sudo firewall-cmd --permanent --add-service=nfs
    sudo firewall-cmd --reload
    Verify: sudo firewall-cmd --query-service=nfs → should return yes.
  3. sudo firewall-cmd --permanent --add-port=9000/tcp
    sudo firewall-cmd --reload
    sudo firewall-cmd --query-port=9000/tcp → should return yes.
  4. The likely cause is that firewall-cmd --reload was not run after the --permanent change. 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.
  5. sudo firewall-cmd --get-zone-of-interface=ens3
    Also accept: sudo firewall-cmd --get-active-zones which shows all interfaces and their zones.
  6. 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

  1. 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.
  2. Always use --permanent for exam tasks. firewall-cmd --permanent --add-service=SERVICE saves the rule to disk. firewall-cmd --reload applies it to the running configuration. Both steps are required — permanent without reload is not active; runtime without permanent is not persistent.
  3. Use service names when possible; raw ports for custom applications. --add-service=http opens port 80/tcp by name. --add-port=8080/tcp opens a specific port directly. List available services with firewall-cmd --get-services.
  4. Verify with --list-all and --query-service. firewall-cmd --list-all shows the running configuration. firewall-cmd --permanent --list-all shows 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-all to record the baseline firewall configuration. Confirm which services are currently allowed in the public zone.
  • Install httpd and start it with systemctl enable --now. Confirm from another terminal or browser that HTTP is blocked by the firewall (connection refused). Then add http permanently, reload, and confirm it is accessible.
  • Add port 8080/tcp to the default zone permanently. Reload and verify with firewall-cmd --query-port=8080/tcp. Also verify with firewall-cmd --list-all that it appears in the ports section.
  • Deliberately add a service without --permanent: firewall-cmd --add-service=ftp. Run firewall-cmd --reload and observe that ftp disappears from the running configuration. This demonstrates the consequence of omitting --permanent.
  • Remove port 8080/tcp permanently with --remove-port=8080/tcp. Reload and verify the port is no longer listed. Run firewall-cmd --permanent --list-all to confirm the permanent configuration no longer includes it.
  • Reboot the system. After reboot, run firewall-cmd --list-all to confirm that http is still allowed (added with --permanent) and 8080/tcp is gone (removed with --permanent). This is the definitive persistence check.
RHCSA Objective

"Restrict network access using firewalld and firewall-cmd." Always --permanent then --reload. Verify with --list-all and --query-service. Reboot confirms persistence.