College-Level Course Module | RHEL System Administration
Learning Objectives
1
Understand firewalld concepts
Zones, services, ports, and runtime vs permanent configuration
2
Configure firewall rules with firewall-cmd
Allow services, open ports, manage zones
3
Manage SELinux port bindings
Allow services to bind to non-standard ports
4
Troubleshoot network access issues
Diagnose firewall blocks and SELinux denials
Defense in Depth
Network security uses multiple layers. The firewall controls external access; SELinux controls internal service behavior. Both must allow traffic for connections to succeed.
Layer 1: Firewall (firewalld)
Controls which external connections reach the system
↓ If allowed ↓
Layer 2: SELinux Port Binding
Controls which services can listen on which ports
↓ If allowed ↓
Layer 3: Service Configuration
The actual application listening and responding
Troubleshooting order: When connections fail, check: (1) Is the service running? (2) Is the firewall allowing traffic? (3) Is SELinux allowing the port binding?
Introduction to firewalld
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
Default behavior: firewalld is enabled by default in RHEL. It blocks all incoming connections except those explicitly allowed, while permitting all outgoing connections.
Firewall Zones
Zones define trust levels for network connections. Each zone has a set of rules determining what traffic is allowed. Interfaces and connections 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
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!
Default zone: The public zone is the default. New interfaces are assigned here unless configured otherwise.
Managing 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# 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 settings for a zone[root@server ~]# firewall-cmd --zone=public --list-all
public (active)
target: default
interfaces: eth0
services: cockpit dhcpv6-client ssh
ports:
...
Runtime vs Permanent
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# This is 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# This is saved but NOT active yet# Apply permanent changes to runtime[root@server ~]# firewall-cmd --reload
# Best practice: Do both at once[root@server ~]# firewall-cmd --add-service=http
[root@server ~]# firewall-cmd --add-service=http --permanent
# Or use --runtime-to-permanent after testing[root@server ~]# firewall-cmd --add-service=http
# Test that it works...[root@server ~]# firewall-cmd --runtime-to-permanent
Adding Services
Services are predefined port and 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 amanda-k5-client ... http https ... ssh# Show what a service includes[root@server ~]# firewall-cmd --info-service=http
http
ports: 80/tcp
protocols:
...# 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
# List services in the current zone[root@server ~]# firewall-cmd --list-services
cockpit dhcpv6-client http https ssh
Opening Ports
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 configuration[root@server ~]# firewall-cmd --list-all
Best practice: Use services when available. Only use raw port numbers for custom applications without predefined service definitions.
Rich Rules
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
Common Tasks
# 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
Pro tip: Use --timeout=SECONDS to test firewall changes. If you lock yourself out, the rule auto-reverts!
SELinux Port Bindings
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.
Firewall allows port 8080
→
SELinux: Can httpd bind to 8080?
→
Service starts (or fails)
# 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
...
Adding Port Bindings
# 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 (if port already assigned to different type)[root@server ~]# semanage port -m -t http_port_t -p tcp 8888
Note: semanage requires the policycoreutils-python-utils package. Install if missing: dnf install policycoreutils-python-utils
Common Port Types
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
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...
SELinux Troubleshooting
# 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:
The boolean nis_enabled was set incorrectly.
...OR...
Missing type enforcement (TE) allow rule.
You can use audit2allow to generate a loadable module...# 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!
Combining Firewall + SELinux
When running a service on a non-standard port, you typically need to configure both the firewall AND SELinux. Missing either will cause 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[root@server ~]# ss -tlnp | grep 8888
LISTEN 0 128 *:8888 *:* users:(("httpd",pid=1234,fd=4))[root@server ~]# curl http://localhost:8888
<html>...</html>
Troubleshooting Flow
1. Is the service running? systemctl status SERVICE | ss -tlnp | grep PORT
↓ Yes ↓
2. Is SELinux allowing the port binding? semanage port -l | grep PORT | ausearch -m avc
↓ Yes ↓
3. Is the firewall allowing traffic? firewall-cmd --list-all | firewall-cmd --list-ports