CIS238RH | RHEL System Administration
Mesa Community College
Zones, services, ports, and runtime vs permanent configuration
Allow services, open ports, manage zones
Allow services to bind to non-standard ports
Diagnose firewall blocks and SELinux denials
Network security uses multiple layers. The firewall controls external access; SELinux controls internal service behavior. Both must allow traffic for connections to succeed.
firewalld is RHEL's dynamic firewall manager. It provides a frontend to the kernel's netfilter framework, allowing runtime changes without disrupting existing connections.
# Check firewalld status
[root@server ~]# systemctl status firewalld
● firewalld.service - firewalld - dynamic firewall daemon
Loaded: loaded (/usr/lib/systemd/system/firewalld.service; enabled)
Active: active (running) since Mon 2024-01-20 10:00:00 EST
# firewalld should be enabled and running by default
[root@server ~]# systemctl enable --now firewalld
# Check if firewalld is running
[root@server ~]# firewall-cmd --state
running
Zones define trust levels for network connections. Each zone has rules determining what traffic is allowed. Interfaces are assigned to zones.
| Zone | Trust Level | Default Behavior |
|---|---|---|
| drop | None | Drop all incoming, no reply sent |
| block | None | Reject all incoming with ICMP message |
| public | Low | Default zone — ssh and dhcpv6-client only |
| external | Low | For routers — masquerading enabled |
| dmz | Low | For DMZ servers — limited access |
| Zone | Trust Level | Default Behavior |
|---|---|---|
| work | Medium | For work networks — ssh, dhcpv6-client |
| home | Medium | For home networks — more services allowed |
| internal | High | For internal networks — similar to home |
| trusted | Full | All traffic accepted — use with caution! |
public zone is the default. New interfaces are assigned here unless configured otherwise. You can assign different interfaces to different zones.
# List all available zones
[root@server ~]# firewall-cmd --get-zones
block dmz drop external home internal nm-shared public trusted work
# Show the default zone
[root@server ~]# firewall-cmd --get-default-zone
public
# Show active zones and their interfaces
[root@server ~]# firewall-cmd --get-active-zones
public
interfaces: eth0 eth1
# Show all settings for a zone
[root@server ~]# firewall-cmd --zone=public --list-all
public (active)
target: default
interfaces: eth0
services: cockpit dhcpv6-client ssh
ports:
...
# Change the default zone
[root@server ~]# firewall-cmd --set-default-zone=internal
# Assign an interface to a different zone
[root@server ~]# firewall-cmd --zone=internal --change-interface=eth1 --permanent
[root@server ~]# firewall-cmd --reload
# Show all zones at once
[root@server ~]# firewall-cmd --list-all-zones
Critical concept! firewalld maintains two configurations: runtime (active now, lost on reload/reboot) and permanent (saved, applied on reload/reboot).
# Runtime change — immediate but temporary
[root@server ~]# firewall-cmd --add-service=http
success
# Active NOW but lost after reload or reboot
# Permanent change — saved but not active until reload
[root@server ~]# firewall-cmd --add-service=http --permanent
success
# Saved but NOT active yet — must reload
# Apply permanent changes to runtime
[root@server ~]# firewall-cmd --reload
# Best practice: apply both at once
[root@server ~]# firewall-cmd --add-service=http
[root@server ~]# firewall-cmd --add-service=http --permanent
# Or: test first, then save working config
[root@server ~]# firewall-cmd --add-service=http
# Verify it works...
[root@server ~]# firewall-cmd --runtime-to-permanent
--runtime-to-permanent to save your working configuration. Never commit permanent changes without testing them first.
Services are predefined port/protocol combinations. Using services is preferred over raw port numbers — they are self-documenting and can include multiple ports.
# List all available predefined services
[root@server ~]# firewall-cmd --get-services
RH-Satellite-6 amanda-client ... http https ... ssh
# Show what a service includes
[root@server ~]# firewall-cmd --info-service=http
http
ports: 80/tcp
protocols:
...
# List services in the current zone
[root@server ~]# firewall-cmd --list-services
cockpit dhcpv6-client http https ssh
# Add a service to the default zone
[root@server ~]# firewall-cmd --add-service=http --permanent
[root@server ~]# firewall-cmd --add-service=https --permanent
[root@server ~]# firewall-cmd --reload
# Add service to a specific zone
[root@server ~]# firewall-cmd --zone=internal --add-service=nfs --permanent
# Remove a service
[root@server ~]# firewall-cmd --remove-service=http --permanent
--zone applies the change to the default zone. Always add --permanent so changes survive reboot, then --reload to activate.
When no predefined service exists, open ports directly. Specify the port number and protocol (tcp or udp).
# Open a specific port
[root@server ~]# firewall-cmd --add-port=8080/tcp --permanent
[root@server ~]# firewall-cmd --reload
# Open a range of ports
[root@server ~]# firewall-cmd --add-port=5000-5100/tcp --permanent
# Open UDP port
[root@server ~]# firewall-cmd --add-port=53/udp --permanent
# Open port in specific zone
[root@server ~]# firewall-cmd --zone=internal --add-port=3306/tcp --permanent
# List open ports
[root@server ~]# firewall-cmd --list-ports
8080/tcp 5000-5100/tcp
# Remove a port
[root@server ~]# firewall-cmd --remove-port=8080/tcp --permanent
# Verify complete configuration
[root@server ~]# firewall-cmd --list-all
--list-ports shows directly opened ports; --list-all shows the complete picture.
Rich rules provide fine-grained control — allowing traffic from specific sources, logging, rate limiting, and more complex conditions.
# Allow HTTP only from specific network
[root@server ~]# firewall-cmd --add-rich-rule='rule family="ipv4" source address="192.168.1.0/24" service name="http" accept' --permanent
# Allow SSH from single IP
[root@server ~]# firewall-cmd --add-rich-rule='rule family="ipv4" source address="10.0.0.50" service name="ssh" accept' --permanent
# Block specific IP
[root@server ~]# firewall-cmd --add-rich-rule='rule family="ipv4" source address="192.168.1.100" drop' --permanent
# Allow port with logging
[root@server ~]# firewall-cmd --add-rich-rule='rule family="ipv4" port port="8080" protocol="tcp" log prefix="Custom8080: " accept' --permanent
# List rich rules
[root@server ~]# firewall-cmd --list-rich-rules
# Remove a rich rule (must match exactly)
[root@server ~]# firewall-cmd --remove-rich-rule='rule family="ipv4" source address="192.168.1.100" drop' --permanent
--list-rich-rules to confirm the exact text.
# Web server setup (HTTP and HTTPS)
[root@server ~]# firewall-cmd --add-service={http,https} --permanent
[root@server ~]# firewall-cmd --reload
# Database server (MySQL) — internal zone only
[root@server ~]# firewall-cmd --zone=internal --add-service=mysql --permanent
# Custom application on port 8443
[root@server ~]# firewall-cmd --add-port=8443/tcp --permanent
[root@server ~]# firewall-cmd --reload
# Check what is allowed
[root@server ~]# firewall-cmd --list-all
public (active)
target: default
interfaces: eth0
services: cockpit dhcpv6-client http https ssh
ports: 8443/tcp
...
# Temporarily test a change (auto-reverts after timeout)
[root@server ~]# firewall-cmd --add-service=ftp --timeout=300
# FTP allowed for 5 minutes, then automatically removed
# Brace expansion works for services and ports
[root@server ~]# firewall-cmd --add-service={http,https,ftp} --permanent
[root@server ~]# firewall-cmd --add-port={8080,8443}/tcp --permanent
--timeout=SECONDS to test firewall changes. If you lock yourself out, the rule auto-reverts — a critical safety net when working over SSH.
SELinux port bindings control which SELinux types (services) can bind to which network ports. Even if the firewall allows traffic, SELinux must permit the service to use that port.
# List all SELinux port bindings
[root@server ~]# semanage port -l
SELinux Port Type Proto Port Number
http_port_t tcp 80, 81, 443, 488, 8008, 8009, 8443, 9000
ssh_port_t tcp 22
...
# Filter for specific service type
[root@server ~]# semanage port -l | grep http
http_port_t tcp 80, 81, 443, 488, 8008, 8009, 8443, 9000
...
# Check if a port is already assigned
[root@server ~]# semanage port -l | grep 8080
http_port_t tcp 80, 81, 443, 488, 8008, 8009, 8443, 9000
# 8080 is included in http_port_t — httpd can use it
# Add a new port to an existing type
[root@server ~]# semanage port -a -t http_port_t -p tcp 8888
# Now httpd can bind to port 8888
# Verify the addition
[root@server ~]# semanage port -l | grep 8888
http_port_t tcp 8888
# Add port for SSH on non-standard port
[root@server ~]# semanage port -a -t ssh_port_t -p tcp 2222
# Remove a port assignment
[root@server ~]# semanage port -d -t http_port_t -p tcp 8888
# Modify existing assignment (port already assigned to different type)
[root@server ~]# semanage port -m -t http_port_t -p tcp 8888
semanage requires the policycoreutils-python-utils package.dnf install policycoreutils-python-utils
-m (modify) when a port is already assigned to a different type.
| Port Type | Services | Default Ports |
|---|---|---|
| http_port_t | Apache, Nginx, web servers | 80, 443, 8008, 8009, 8443 |
| ssh_port_t | OpenSSH server | 22 |
| mysqld_port_t | MySQL, MariaDB | 3306 |
| postgresql_port_t | PostgreSQL | 5432 |
http_port_t already includes many common web ports — you often don't need to add anything unless using an unusual port. ssh_port_t only includes port 22 by default; always add non-standard SSH ports.
| Port Type | Services | Default Ports |
|---|---|---|
| dns_port_t | BIND, DNS servers | 53 |
| smtp_port_t | Postfix, mail servers | 25, 465, 587 |
| ftp_port_t | vsftpd, FTP servers | 21 |
| nfs_port_t | NFS server | 2049 |
# Find the port type for a specific service
[root@server ~]# semanage port -l | grep -i mysql
mysqld_port_t tcp 1186, 3306, 63132-63164
# Find what type a specific port uses
[root@server ~]# semanage port -l | grep " 80"
http_port_t tcp 80, 81, 443, 488, 8008...
# Scenario: Apache configured for port 8888 but fails to start
[root@server ~]# systemctl start httpd
Job for httpd.service failed...
# Check SELinux denials in audit log
[root@server ~]# ausearch -m avc -ts recent
type=AVC msg=... denied { name_bind } for ...
src=8888 ... scontext=system_u:system_r:httpd_t:s0
tcontext=system_u:object_r:unreserved_port_t:s0 ...
# Use audit2why for explanation
[root@server ~]# ausearch -m avc -ts recent | audit2why
Was caused by:
Missing type enforcement (TE) allow rule.
# Get specific fix suggestion
[root@server ~]# ausearch -m avc -ts recent | audit2allow
#============= httpd_t ==============
allow httpd_t unreserved_port_t:tcp_socket name_bind;
# Better solution: add port to http_port_t
[root@server ~]# semanage port -a -t http_port_t -p tcp 8888
[root@server ~]# systemctl start httpd # Now works!
semanage port -a is the correct, targeted fix.
When running a service on a non-standard port, configure both the firewall and SELinux. Missing either causes connection failures.
# Example: Run Apache on port 8888
# Step 1: Configure Apache to listen on 8888
[root@server ~]# vi /etc/httpd/conf/httpd.conf
# Change: Listen 8888
# Step 2: Add SELinux port binding
[root@server ~]# semanage port -a -t http_port_t -p tcp 8888
# Step 3: Open firewall port
[root@server ~]# firewall-cmd --add-port=8888/tcp --permanent
[root@server ~]# firewall-cmd --reload
# Step 4: Start/restart the service
[root@server ~]# systemctl restart httpd
# Step 5: Verify service is listening
[root@server ~]# ss -tlnp | grep 8888
LISTEN 0 128 *:8888 *:* users:(("httpd",pid=1234,fd=4))
# Step 5b: Test local access
[root@server ~]# curl http://localhost:8888
<html>...</html>
systemctl status SERVICE | ss -tlnp | grep PORTsemanage port -l | grep PORT | ausearch -m avcfirewall-cmd --list-all | firewall-cmd --list-portsping | traceroute | client firewall# Check if service is listening
[root@server ~]# ss -tlnp | grep httpd
LISTEN 0 128 *:80 *:* users:(("httpd",pid=1234...))
# Check firewall status
[root@server ~]# firewall-cmd --list-all
# Check SELinux mode
[root@server ~]# getenforce
Enforcing
# Check SELinux port assignments
[root@server ~]# semanage port -l | grep http_port_t
# Check for SELinux denials
[root@server ~]# ausearch -m avc -ts recent
# Test connectivity from local
[root@server ~]# curl http://localhost:80
# Test connectivity from remote (on client)
[user@client ~]$ curl http://server:80
[user@client ~]$ nc -zv server 80
Connection to server 80 port [tcp/http] succeeded!
# Check iptables rules (low-level)
[root@server ~]# iptables -L -n | head -20
nc -zv tests raw TCP without HTTP, helping separate network from application problems.
| Scenario | Firewall | SELinux |
|---|---|---|
| Web server on port 80/443 | --add-service={http,https} |
Default ports — no change needed |
| Web server on port 8080 | --add-port=8080/tcp |
Default in http_port_t — no change needed |
| Web server on port 9999 | --add-port=9999/tcp |
semanage port -a -t http_port_t -p tcp 9999 |
| Scenario | Firewall | SELinux |
|---|---|---|
| SSH on port 2222 | --add-port=2222/tcp |
semanage port -a -t ssh_port_t -p tcp 2222 |
| MySQL internal only | --zone=internal --add-service=mysql |
Default port — no change needed |
| MySQL on port 13306 | --add-port=13306/tcp |
semanage port -a -t mysqld_port_t -p tcp 13306 |
/etc/ssh/sshd_config (Port 2222) and restarting sshd. Always use --timeout when testing SSH port changes!
--permanent with firewall-cmd--timeout before committing--list-all after every changetrusted zone for internet-facing interfaces--permanent (changes lost on reboot)--timeout--timeout. If you accidentally block SSH, the rule auto-reverts and you regain access.
Firewall (firewalld): Controls external access. Use firewall-cmd to add services/ports. Always use --permanent.
Zones: Group interfaces by trust level. Default is public. Use appropriate zones for different networks.
SELinux Ports: Controls which services can bind to which ports. Use semanage port -a for non-standard ports.
Both Required: Non-standard ports need both firewall AND SELinux configuration. Check both when troubleshooting.
--timeout to safely test firewall changesNext: Network Attached Storage