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.
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 overviewCode: Select all
# Edit /etc/selinux/config and set:
SELINUX=enforcing # or permissive
# A change to/from 'disabled' requires a reboot.─────────────────────────────────────────
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 contextCode: 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/htmlCode: 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─────────────────────────────────────────
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 oneCode: Select all
setsebool httpd_can_network_connect on # this session only
setsebool -P httpd_can_network_connect on # permanent (survives reboot)- 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
─────────────────────────────────────────
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─────────────────────────────────────────
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 zone5 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 changesCode: Select all
firewall-cmd --permanent --remove-service=http
firewall-cmd --reload─────────────────────────────────────────
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 --reloadCode: Select all
firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="203.0.113.45" reject'
firewall-cmd --reloadCode: 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─────────────────────────────────────────
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.
─────────────────────────────────────────
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.