RED HAT ENTERPRISE LINUX

Firewall & SELinux

Controlling Network Access to Services

CIS238RH | RHEL System Administration
Mesa Community College

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: (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.

Default behavior: firewalld is enabled by default in RHEL. It blocks all incoming connections except those explicitly allowed, while permitting all outgoing connections.

firewalld – Basic Commands

# 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
Never disable firewalld on production systems — configure it to allow the traffic you need.

Firewall Zones

Zones define trust levels for network connections. Each zone has rules determining what traffic is allowed. Interfaces are assigned to zones.

ZoneTrust LevelDefault Behavior
dropNoneDrop all incoming, no reply sent
blockNoneReject all incoming with ICMP message
publicLowDefault zone — ssh and dhcpv6-client only
externalLowFor routers — masquerading enabled
dmzLowFor DMZ servers — limited access

Firewall Zones (continued)

ZoneTrust LevelDefault Behavior
workMediumFor work networks — ssh, dhcpv6-client
homeMediumFor home networks — more services allowed
internalHighFor internal networks — similar to home
trustedFullAll traffic accepted — use with caution!
Default zone: The public zone is the default. New interfaces are assigned here unless configured otherwise. You can assign different interfaces to different zones.

Managing Zones — Listing

# 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: 
  ...

Managing Zones — Modifying

# 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
--set-default-zone affects new interfaces but not existing assignments. Use --change-interface to move an existing interface to a different zone.

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
# 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

Runtime vs Permanent — Best Practice

# 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
Workflow: Make runtime changes → test → use --runtime-to-permanent to save your working configuration. Never commit permanent changes without testing them first.

Adding Services

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

Adding Services — Commands

# 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
Omitting --zone applies the change to the default zone. Always add --permanent so changes survive reboot, then --reload to activate.

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

Opening Ports — Verify & Remove

# 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
Best practice: Use services when available. Only use raw port numbers for custom applications without predefined service definitions. --list-ports shows directly opened ports; --list-all shows the complete picture.

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

Rich Rules — Logging & Management

# 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
Rich rules are evaluated in order; the first match wins. To remove a rule, the syntax must match exactly what was used when adding it. Use --list-rich-rules to confirm the exact text.

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
  ...

Common Tasks — Safe Testing

# 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
Pro tip: Use --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

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)

SELinux Port Bindings — Listing

# 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
...
If SELinux is in enforcing mode and a port is not assigned to the right type, the service cannot bind to it — regardless of firewall settings. Always check both layers.

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

Adding Port Bindings — Modify & Remove

# 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
Note: semanage requires the policycoreutils-python-utils package.
Install if missing: dnf install policycoreutils-python-utils
You cannot delete default port assignments that came with the SELinux policy — only ones you added yourself. Use -m (modify) when a port is already assigned to a different type.

Common Port Types

Port TypeServicesDefault Ports
http_port_tApache, Nginx, web servers80, 443, 8008, 8009, 8443
ssh_port_tOpenSSH server22
mysqld_port_tMySQL, MariaDB3306
postgresql_port_tPostgreSQL5432
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.

Common Port Types (continued)

Port TypeServicesDefault Ports
dns_port_tBIND, DNS servers53
smtp_port_tPostfix, mail servers25, 465, 587
ftp_port_tvsftpd, FTP servers21
nfs_port_tNFS server2049
# 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:
Missing type enforcement (TE) allow rule.

SELinux Troubleshooting — Fix

# 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!
Prefer semanage over audit2allow. Creating custom policy modules with audit2allow is a last resort. Adding the port to the appropriate type with semanage port -a is the correct, targeted fix.

Combining Firewall + SELinux

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

Combining Firewall + SELinux — Verify

# 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>
Troubleshooting guide: Service not listening → check service config + SELinux. Local curl fails → service not started. Remote access fails → check firewall.

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
↓ Yes ↓
4. Check network/routing/client issues
ping | traceroute | client firewall

Diagnostic Commands

# 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

Diagnostic Commands — Connectivity

# 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
If local curl works but remote fails → firewall issue. If local curl fails → service or SELinux issue. nc -zv tests raw TCP without HTTP, helping separate network from application problems.

Common Scenarios

ScenarioFirewallSELinux
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
Pattern: Standard and common ports usually need only firewall changes. Unusual non-standard ports need both firewall AND SELinux configuration.

Common Scenarios (continued)

ScenarioFirewallSELinux
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
Moving SSH to a non-standard port also requires updating /etc/ssh/sshd_config (Port 2222) and restarting sshd. Always use --timeout when testing SSH port changes!

Best Practices — Do

✔ Do

  • Use services instead of raw ports when possible
  • Always use --permanent with firewall-cmd
  • Test changes with --timeout before committing
  • Use zones to segment network trust levels
  • Keep SELinux in enforcing mode
  • Document all port and service changes
  • Use rich rules for source-based restrictions
  • Verify with --list-all after every change

Best Practices — Do Not

✘ Do Not

  • Disable firewalld on production systems
  • Set SELinux to permissive/disabled permanently
  • Open ports without understanding what uses them
  • Use the trusted zone for internet-facing interfaces
  • Forget --permanent (changes lost on reboot)
  • Ignore SELinux denials — fix them properly
  • Skip testing after firewall changes
  • Make firewall changes via SSH without --timeout
Critical: When making firewall changes via SSH, always use --timeout. If you accidentally block SSH, the rule auto-reverts and you regain access.

Key Takeaways

1

Firewall (firewalld): Controls external access. Use firewall-cmd to add services/ports. Always use --permanent.

2

Zones: Group interfaces by trust level. Default is public. Use appropriate zones for different networks.

3

SELinux Ports: Controls which services can bind to which ports. Use semanage port -a for non-standard ports.

4

Both Required: Non-standard ports need both firewall AND SELinux configuration. Check both when troubleshooting.

Graded Lab

  • Configure Apache on port 8888 (firewall + SELinux)
  • Move SSH to port 2222 (both configurations required)
  • Create rich rule allowing HTTP only from 192.168.1.0/24
  • Troubleshoot a service that fails to start (SELinux denial)
  • Configure different zones for different interfaces
  • Use --timeout to safely test firewall changes

Next: Network Attached Storage

1 / 38
Managing Network Security