Page 1 of 1

Linux Security — SELinux & Firewalld Explained Simply

Posted: Sat Jun 13, 2026 1:20 pm
by Murali Krishna
Linux Security — SELinux & Firewalld Explained Simply
A clear, practical guide to enforcing modes, contexts, booleans, firewall zones and rich rules, with copy-ready commands (AlmaLinux 9 / RHEL 9)

─────────────────────────────────────────
Two guards stand between attackers and your server.
Firewalld controls which network traffic is even allowed in — like a guard at the building's front gate. SELinux controls what each program is allowed to do once it's inside — like a guard checking that every worker only enters the rooms their badge permits. This guide covers both.
─────────────────────────────────────────

Part 1 — SELinux

SELinux (Security-Enhanced Linux) is a mandatory access control system. In plain terms: even if a hacker takes over a service, SELinux limits the damage by only letting that service touch the exact files and ports it's supposed to. Every file and process carries a label, and a central policy decides which labels may interact.

Tip: The golden rule of SELinux — don't disable it when something breaks. Almost every "SELinux problem" is fixed with the right context or boolean, not by turning the guard off.

─────────────────────────────────────────

1 Enforcing, Permissive & Disabled — the three modes
  • Enforcing — the policy is active and blocks violations. This is the secure, recommended mode.
  • Permissive — nothing is blocked, but every violation is LOGGED. Perfect for troubleshooting and testing new policy.
  • Disabled — SELinux is off entirely. Not recommended.
Check and change the mode temporarily

Code: Select all

getenforce              # show current mode
setenforce 0            # switch to Permissive (until reboot)
setenforce 1            # switch back to Enforcing (until reboot)
sestatus                # full status overview
Set the mode permanently

Code: Select all

# Edit /etc/selinux/config and set:
SELINUX=enforcing       # or permissive
# A change to/from 'disabled' requires a reboot.
Tip: When a service misbehaves, flip to Permissive, reproduce the issue, read the logged denials, fix the context/boolean, then return to Enforcing. You troubleshoot without leaving the system exposed for long.

─────────────────────────────────────────

2 Contexts — the labels on everything

Every file, process and port has an SELinux context, written as four parts: user:role:type:level. The part that matters most day-to-day is the type (e.g. httpd_sys_content_t). SELinux allows access by matching types — a web server labelled httpd_t can only read files labelled as web content.

View contexts

Code: Select all

ls -Z /var/www/html              # file contexts
ps -eZ | grep nginx              # process contexts
id -Z                            # your own context
The classic problem — files in the wrong place get the wrong label

Code: Select all

# Move content into a custom path and the web server gets "Permission denied"
# even though normal permissions look fine. The fix is the LABEL, not chmod.

# Temporarily set a context:
chcon -t httpd_sys_content_t /srv/web/index.html

# Reset a file back to its policy-defined default:
restorecon -Rv /var/www/html
Make a custom path's label permanent (the right way)

Code: Select all

# Tell the policy that /srv/web should always be web content:
semanage fcontext -a -t httpd_sys_content_t "/srv/web(/.*)?"
restorecon -Rv /srv/web          # apply it
WARNING: chcon is temporary — a relabel or restorecon wipes it. For anything permanent, use semanage fcontext + restorecon so the label survives.

─────────────────────────────────────────

3 Booleans — on/off switches for policy

Booleans are pre-built toggle switches that turn optional SELinux behaviours on or off, without writing any policy yourself. For example, allowing the web server to make outbound network connections.

List and read booleans

Code: Select all

getsebool -a                     # list every boolean
getsebool httpd_can_network_connect   # check one
Flip a boolean (use -P to make it permanent)

Code: Select all

setsebool httpd_can_network_connect on        # this session only
setsebool -P httpd_can_network_connect on     # permanent (survives reboot)
Handy common booleans
  • httpd_can_network_connect — let the web server reach other services/DBs
  • httpd_enable_homedirs — serve files from user home directories
  • ftpd_full_access — let FTP write across the filesystem
  • samba_enable_home_dirs — share home directories over Samba
Tip: Always use -P. Without it, your change vanishes on reboot and you'll be debugging the "same" problem next week.

─────────────────────────────────────────

4 Troubleshooting SELinux denials

When SELinux blocks something, it logs an AVC (Access Vector Cache) denial. These tools turn the cryptic log into a plain explanation and a suggested fix.

Code: Select all

ausearch -m avc -ts recent       # recent denials
audit2why < /var/log/audit/audit.log    # explain WHY it was denied
# audit2allow suggests a policy fix - READ it, don't blindly apply:
ausearch -m avc -ts recent | audit2allow -M mypol
WARNING: audit2allow makes it easy to generate an "allow everything" module that silently defeats SELinux. Read the suggested rule, and prefer fixing the context or boolean first — only build a custom module when there's genuinely no standard fix.

─────────────────────────────────────────

Part 2 — Firewalld

Firewalld is the front-gate guard. It's the default firewall manager on AlmaLinux 9 and organises rules into zones — named trust levels (public, internal, trusted, etc.) that you assign to network interfaces.

See the current state

Code: Select all

firewall-cmd --state                 # is it running?
firewall-cmd --get-active-zones      # which zones are in use
firewall-cmd --list-all              # everything allowed in the default zone
─────────────────────────────────────────

5 Firewalld basics — services & ports

Two key ideas: --permanent writes the rule to disk (survives reboot), and you must --reload for permanent rules to take effect.

Allow a service or a specific port

Code: Select all

firewall-cmd --permanent --add-service=https     # allow HTTPS
firewall-cmd --permanent --add-port=8080/tcp     # allow a custom port
firewall-cmd --reload                            # apply permanent changes
Remove access

Code: Select all

firewall-cmd --permanent --remove-service=http
firewall-cmd --reload
Tip: Test a rule live without --permanent first. If it works, add it again WITH --permanent. A rule added live but not made permanent disappears on the next reload/reboot.

─────────────────────────────────────────

6 Rich Rules — fine-grained control

Plain rules say "allow this port." Rich rules add conditions: which source IP, which action, whether to log, rate limits, and more. They're how you express precise policy.

Allow SSH only from one trusted network

Code: Select all

firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="192.168.29.0/24" service name="ssh" accept'
firewall-cmd --reload
Block a specific abusive IP

Code: Select all

firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="203.0.113.45" reject'
firewall-cmd --reload
Rate-limit and log new connections to a port

Code: Select all

firewall-cmd --permanent --add-rich-rule='rule family="ipv4" port port="8080" protocol="tcp" log prefix="WEB8080 " level="info" limit value="5/m" accept'
firewall-cmd --reload
Tip: Rich rules are evaluated with reject/drop taking priority where it matters — build allowlists (accept from trusted sources) rather than trying to block every bad actor one by one.

─────────────────────────────────────────

7 Security Policies — putting it together

Good server security layers these controls so a failure in one is caught by another:
  • Firewalld — only expose the ports you actually serve; restrict admin ports (SSH) to known source networks via rich rules.
  • SELinux in Enforcing — contains any service that does get compromised, so a breached web server can't read your database files or shells.
  • Booleans over disabling — grant the minimum extra permission needed, never switch SELinux off.
  • Correct contexts — label custom paths properly with semanage fcontext so services work without weakening policy.
  • Audit & review — watch denials and firewall logs; they're early warning of both misconfiguration and attack.
Core principle — least privilege: every service should have exactly the access it needs and nothing more. Firewalld enforces it at the network edge; SELinux enforces it inside the host.

─────────────────────────────────────────

Quick Reference Cheat Sheet
  • SELinux mode — getenforce ; sestatus
  • Temp mode — setenforce 0|1
  • Permanent mode — edit /etc/selinux/config
  • View contexts — ls -Z ; ps -eZ
  • Fix context (temp) — chcon -t TYPE file
  • Reset context — restorecon -Rv /path
  • Permanent context — semanage fcontext -a -t TYPE "/path(/.*)?" ; restorecon
  • List booleans — getsebool -a
  • Set boolean — setsebool -P name on
  • Explain denial — ausearch -m avc -ts recent | audit2why
  • Firewall status — firewall-cmd --list-all
  • Allow service — firewall-cmd --permanent --add-service=https ; --reload
  • Allow port — firewall-cmd --permanent --add-port=8080/tcp ; --reload
  • Rich rule — firewall-cmd --permanent --add-rich-rule='...' ; --reload
─────────────────────────────────────────

Do you run SELinux in Enforcing on production, or do you switch it off? And what's your firewalld zone strategy? Share your approach below.