Red Hat Certified System Administrator (RHCSA) #13: SELinux in depth — contexts, booleans, troubleshooting (audit2allow)
In #12 firewalld and SSH key authentication we opened ports and logged in with keys. But if you opened the firewall and brought up the service and connections are still blocked, the next suspect is almost always SELinux. SELinux is the area where RHCSA candidates trip up most often, and at the same time the area where, once you have the patterns down, you most reliably bank points. This post pins down contexts, booleans, port labels, and troubleshooting from a hands-on exam perspective.
The core of SELinux is simple. Every process and file gets a label (context), and only the combinations the policy allows pass through. Even an operation that clears every standard Linux permission (rwx) will fail if the SELinux policy denies it. Nearly every SELinux problem you meet on RHCSA shows up as “the permissions are right, but it doesn’t work.”
SELinux modes: enforcing, permissive, disabled #
SELinux has three modes.
| Mode | Behavior |
|---|---|
| enforcing | Enforces the policy. Denied operations are blocked and recorded in the audit log |
| permissive | Does not enforce the policy. Nothing is blocked, but denials are still recorded to the log |
| disabled | SELinux off. Labeling stops too |
permissive is the “doesn’t block, but logs what would have been blocked” mode, which makes it very useful during troubleshooting.
Checking the current mode and switching temporarily #
You read the current mode with getenforce. sestatus shows more detailed status.
getenforce
# Enforcing
sestatus
# SELinux status: enabled
# Current mode: enforcing
# Mode from config file: enforcing
# Policy from config file: targetedsetenforce switches between enforcing and permissive immediately. But this switch is a temporary setting that disappears on reboot.
setenforce 0 # switch to permissive
setenforce 1 # switch to enforcing
getenforce
# Permissivesetenforce can’t take you to disabled. It only moves between enforcing and permissive.
Permanent mode: /etc/selinux/config #
The mode that survives a reboot is set by the SELINUX= value in /etc/selinux/config. When RHCSA gives you a task like “set SELinux permanently to enforcing,” this is the file you edit.
cat /etc/selinux/config
# SELINUX=enforcing
# SELINUXTYPE=targetedChanging the SELINUX= value to one of enforcing, permissive, or disabled takes effect from the next boot. The exam usually asks for permanent enforcing, so the safe move is to apply it immediately with setenforce 1 and also write enforcing into the config file.
setenforce 1
sed -i 's/^SELINUX=.*/SELINUX=enforcing/' /etc/selinux/config
grep '^SELINUX=' /etc/selinux/config
# SELINUX=enforcingWhen you switch from disabled to enforcing, labels may be missing across the whole file system, so an automatic relabel happens on the next boot. To force a relabel, touch /.autorelabel and reboot.
Context: the structure of a label #
SELinux attaches a context to every object. A context is made of four parts separated by colons.
user:role:type:level
system_u:object_r:httpd_sys_content_t:s0The part you actually deal with on RHCSA is almost always just the type (the third field). That’s because most policy decisions are made through the allow rules between types (type enforcement). For example, the type of a web server process is httpd_t, and the type of a web content file is httpd_sys_content_t. The policy allows “httpd_t may read httpd_sys_content_t,” so the web server serves that file.
Viewing file context: ls -Z #
The -Z option shows the SELinux context. You check a file’s context with ls -Z.
ls -Z /var/www/html/
# unconfined_u:object_r:httpd_sys_content_t:s0 index.htmlViewing process context: ps -Z #
You view a running process’s context with ps -Z.
ps -eZ | grep httpd
# system_u:system_r:httpd_t:s0 1234 ? 00:00:00 httpdid -Z shows the current shell’s context, and ss -Z or netstat -Z shows a socket’s context.
What happens when the context is wrong #
This is the most common scenario on RHCSA. When you put web content somewhere other than the standard location, or move a file in from elsewhere, the context gets out of sync. For example, if you mv a file you created in a home directory to /var/www/html, that file carries over the context of its original location (user_home_t, etc.). As a result the web server process (httpd_t) can’t read it and you get a 403.
There’s a key rule to remember here.
- A
cp(copy) picks up the default context of the destination directory. - An
mv(move) preserves the original’s context as-is.
So after moving a file you have to correct the context.
Fixing the context: chcon vs restorecon vs semanage #
There are three ways to change a context, and which one you use can decide your RHCSA score.
chcon: temporary change (avoid it on the exam) #
chcon changes a file’s context directly. It applies immediately, but it is not reflected in the policy database. So if restorecon runs later or a file system relabel happens, it reverts to the original.
chcon -t httpd_sys_content_t /var/www/html/index.html
chcon -R -t httpd_sys_content_t /web/content # recursive changeUse chcon only for quick tests; it’s not a safe exam answer. If the system gets relabeled before grading, your change will be undone.
semanage fcontext + restorecon: permanent change (the correct answer) #
The correct way to fix a context permanently is two steps.
- Use
semanage fcontext -a -tto add a default context rule to the policy. - Use
restoreconto apply that rule to the actual files.
For example, to use everything under /web as web content, you do this.
# 1) Add the rule to the policy: define everything under /web as httpd_sys_content_t
semanage fcontext -a -t httpd_sys_content_t "/web(/.*)?"
# 2) Apply the rule to the actual files
restorecon -R -v /web"/web(/.*)?" is a regular expression that means the /web directory itself and every path beneath it. This way the rule remains in the policy itself, so the context holds even when a relabel happens. This is the permanent application RHCSA asks for.
semanage is in the policycoreutils-python-utils package. If it’s missing, install it.
dnf install -y policycoreutils-python-utilsTo check or delete a rule, do this.
semanage fcontext -l | grep '/web' # check the rule you added
semanage fcontext -d "/web(/.*)?" # delete the rulerestorecon: reverting to the default context #
restorecon re-applies the default context defined in the policy to a file. A file in a standard location whose context got out of sync from a mv is corrected with just restorecon, no semanage needed. That’s because /var/www/html is already defined as httpd_sys_content_t in the policy.
restorecon -R -v /var/www/html
# Relabeled /var/www/html/index.html
# from unconfined_u:object_r:user_home_t:s0
# to unconfined_u:object_r:httpd_sys_content_t:s0To sum up: for a standard location, restorecon alone; for a non-standard location, add a rule with semanage fcontext and then restorecon.
boolean: turning policy behavior on and off #
There are cases where things are blocked even when the context is correct. SELinux policy has on/off switches called booleans that allow or block specific behaviors at the policy level. For example, behaviors like a web server serving user home directories, or making outbound network connections, are off by default via booleans.
Listing booleans: getsebool #
You see all booleans with getsebool -a. semanage boolean -l also shows the default value and a description.
getsebool -a | grep httpd
# httpd_can_network_connect --> off
# httpd_enable_homedirs --> off
# httpd_use_nfs --> off
semanage boolean -l | grep httpd_can_network_connect
# httpd_can_network_connect (off , off) Allow httpd to ...Setting a boolean: setsebool -P #
You flip a boolean with setsebool. The -P option is the key here. Turn it on without -P and it applies only in memory, so it turns off on reboot. You have to add -P to save it permanently to the policy.
setsebool httpd_can_network_connect on # temporary. turns off on reboot
setsebool -P httpd_can_network_connect on # permanent. the correct answerOn RHCSA, a task like “make a web app that connects to a database work” is answered nine times out of ten by turning on httpd_can_network_connect with -P. Leave off -P and you lose points when the system is rebooted at grading time.
Port labels: semanage port #
SELinux labels ports too. If you run a service on a non-standard port, that port’s label won’t match the service’s type and the bind is denied. For example, if you change sshd from port 22 to port 2222, port 2222 has no ssh_port_t label, so sshd can’t open that port. Opening the port with firewalld does no good — it’s blocked at the SELinux layer.
Viewing current port labels #
semanage port -l | grep ssh
# ssh_port_t tcp 22
semanage port -l | grep http_port_t
# http_port_t tcp 80, 81, 443, 488, 8008, 8009, 8443, 9000Adding a port label: semanage port -a -t #
To use a non-standard port, assign the appropriate type to that port.
# to use sshd on port 2222, give that port ssh_port_t
semanage port -a -t ssh_port_t -p tcp 2222
semanage port -l | grep ssh
# ssh_port_t tcp 2222, 22If the port is already occupied by another type, use -m (modify) instead of -a (add). When you get an already defined error while adding, switching to -m is the standard move.
semanage port -m -t http_port_t -p tcp 8888An RHCSA task that moves a service to a non-standard port is only complete when you handle opening the firewalld port + adding the SELinux port label together as one bundle. Do only one of the two and the connection won’t go through.
Troubleshooting: tracing denial logs and building policy #
Every SELinux denial is recorded in the audit log as an AVC (Access Vector Cache) denial. Reading what was blocked and why out of this log is the starting point of troubleshooting. The log is at /var/log/audit/audit.log.
ausearch: finding denials in the audit log #
You collect recent AVC denials with ausearch.
ausearch -m AVC -ts recent
# type=AVC msg=audit(...): avc: denied { read } for
# pid=1234 comm="httpd" name="index.html"
# scontext=system_u:system_r:httpd_t:s0
# tcontext=unconfined_u:object_r:user_home_t:s0 tclass=fileThe parts you need to read here are clear: comm="httpd" (who), denied { read } (what), tcontext=...user_home_t (against a target with which label). It means the httpd_t process was blocked while trying to read a user_home_t file — a textbook case of a wrong context. The answer is to correct the label with restorecon or semanage fcontext.
sealert: reading setroubleshoot’s advice #
Install the setroubleshoot-server package and, every time a denial occurs, a human-readable analysis and a recommended command are recorded to /var/log/messages. You read the detail with sealert.
dnf install -y setroubleshoot-server
sealert -a /var/log/audit/audit.logsealert often presents a specific command such as setsebool -P ... or restorecon ..., saying “fix this problem with the following command.” But before following the advice verbatim, you need to judge whether it actually reflects the behavior you intended.
audit2allow: building a policy module from denial logs #
For a denial that custom policy is needed for — one that a standard boolean or context fix won’t resolve — you use audit2allow. It takes a denial log as input and generates a policy rule that allows that behavior.
First, you eyeball the rule to confirm what you’d be allowing.
ausearch -m AVC -ts recent | audit2allow
# allow httpd_t user_home_t:file { read };When you judge the rule to be sound, build it into a module and load it.
# create a policy module named myhttpd
ausearch -m AVC -ts recent | audit2allow -M myhttpd
# load the generated module
semodule -i myhttpd.ppaudit2allow -M creates a .te (policy source) and a compiled .pp (policy package) file, and semodule -i adds that .pp to the system policy. You check loaded custom modules with semodule -l and remove one with semodule -r myhttpd.
Troubleshooting priority #
audit2allow is powerful but it is the last resort. Building a module that allows a denial unconditionally can paper over the real cause (a wrong context or a boolean that’s off). The recommended order on RHCSA is as follows.
- Check the context. Look at the label with
ls -Z, and if it’s out of sync, correct it withrestoreconorsemanage fcontext. - Check the boolean. See whether the behavior itself is off through a boolean, and if so, turn it on with
setsebool -P. - Check the port label. If it’s a non-standard port, assign a label with
semanage port. - Build a module with
audit2allowonly for a genuinely custom behavior that none of the above resolves.
Briefly switching to permissive is also useful for diagnosis. If the service works normally under permissive, the cause is clearly SELinux, and you can collect and analyze all the denial logs that accumulated in one go. When the diagnosis is done, be sure to return to enforcing.
Exam points #
- chcon vs restorecon.
chconis temporary and comes undone on relabel. The permanent answer isrestoreconfor a standard location, andsemanage fcontext -a -tthenrestoreconfor a non-standard location. Don’t usechconas an exam answer. - setsebool’s -P. Leave off
-Pand it disappears on reboot, losing you points at grading. Always usesetsebool -Pwhen turning on a boolean. - semanage fcontext regex. Write everything under a directory as
"/path(/.*)?". Wrap it in quotes so the shell doesn’t interpret the asterisk. - A non-standard port is two places. Open the port with firewalld, and assign the SELinux label too with
semanage port -a -tto complete the connection. - Permanent mode is /etc/selinux/config.
setenforceis temporary. For permanent enforcing, verify down toSELINUX=enforcingin the config file. - Troubleshooting tools. The log is
/var/log/audit/audit.log, denial search isausearch -m AVC, advice issealert, and policy generation isaudit2allow -Mthensemodule -i. - If
semanageis missing installpolicycoreutils-python-utils, and ifsealertis missing installsetroubleshoot-server.
Wrap-up #
What this post locked in:
- SELinux modes. Temporary switch with
getenforce/setenforce, permanent setting with/etc/selinux/config. enforcing , permissive , disabled - Context. Check with
ls -Z/ps -Z. The key is the type field.mvcarries the label over,cptakes the destination’s default - Fixing the context.
chconis temporary; permanent issemanage fcontext -a -t+restorecon. For a standard location,restoreconalone is enough - boolean. Check with
getsebool -a, set permanently withsetsebool -P. A per-behavior on/off switch - Port labels. Assign a type to a non-standard port with
semanage port -a -t. One bundle with firewalld - Troubleshooting.
audit.log→ausearch/sealert→ a policy module withaudit2allow. But check the context, boolean, and port first
SELinux is the area where the concepts you worked through in the RHEL practical track get tightened up from an exam perspective. When you see a denial, don’t panic — get into the habit of checking in the order: context, boolean, port, and SELinux becomes a reliable source of points on RHCSA.
Next — Container management #
We’ve finished the security area too. Now we move on to the last big axis of the RHCSA scope, containers.
In #14 Container management: Podman, systemd integration (quadlet), we’ll type our way through the basic Podman commands for running containers without root, pulling and running an image and mounting storage, and the quadlet integration that registers a container as a systemd service so it starts automatically at boot.