SELinux (Security-Enhanced Linux) is a Mandatory Access Control (MAC) system built into the Linux kernel through the Linux Security Modules (LSM) framework. It was originally developed by the NSA and is now maintained as an open source project with Red Hat as a major contributor. It is enabled in enforcing mode by default on RHEL, CentOS Stream, Rocky Linux, AlmaLinux, and Fedora.
The single most important thing to internalize: SELinux is a second, independent access check that runs after normal Linux permissions. If either check denies an action, the action is denied. Correct chmod/chown settings will not fix an SELinux denial, and a correct SELinux label will not fix a permissions problem.
Table of contents¶
- Concepts
- Exam objectives (RHCSA / RHCE)
- Packages and binaries
- Managing SELinux status and modes
- Viewing contexts
- File contexts: chcon, semanage fcontext, restorecon
- Booleans
- Port labels
- Worked example: Apache
- Troubleshooting denials
- SELinux users, roles, and confined users
- Containers and SELinux
- Inspecting and extending policy
- auditd vs SELinux
- Automating SELinux with Ansible (RHCE)
- Practice scenarios
- Common pitfalls
- References
1. Concepts¶
DAC vs MAC¶
| Discretionary Access Control (DAC) | Mandatory Access Control (MAC) | |
|---|---|---|
| Implemented by | Standard UNIX permissions, ACLs | SELinux |
| Decision based on | User/group identity and file mode bits | Labels on subjects (processes) and objects (files, ports, etc.) plus a system-wide policy |
| Who controls it | The owner of the object | The policy; even root cannot override it |
| Failure mode | A compromised process inherits everything its user can do | A compromised process is confined to what its domain is allowed to do |
With DAC alone, a compromised web server running as apache can read anything apache can read. Under SELinux, the httpd_t domain is only allowed to touch files labeled with types such as httpd_sys_content_t, so a compromised httpd cannot read /etc/shadow or another user's home directory, even if the mode bits allow it.
Everything has a label¶
Every process, file, directory, socket, port, and more has a security context with four fields:
user : role : type : level
Example from ls -Z:
system_u:object_r:httpd_sys_content_t:s0 /var/www/html/index.html
| Field | Meaning | Notes |
|---|---|---|
User (_u) |
SELinux identity, not the Linux account | Mapped from Linux users via semanage login. Rarely the cause of problems in targeted policy. |
Role (_r) |
Bridges users and types (RBAC) | Files always use object_r. |
Type (_t) |
The field that matters. Called the domain for processes and the type for files. | Nearly all day-to-day troubleshooting is about types. |
Level (s0, s0:c0.c1023) |
MLS/MCS sensitivity and categories | Used for MCS isolation (containers, VMs) and in the mls policy. |
Type Enforcement¶
The core mechanism is type enforcement (TE): the policy contains allow rules of the form
allow <source domain> <target type> : <object class> { <permissions> };
allow httpd_t httpd_sys_content_t : file { read getattr open };
Anything not explicitly allowed is denied (default deny). A denial is called an AVC (Access Vector Cache) denial and is logged.
Domain transitions¶
When a process executes a file, it can change domain. For example, systemd (init_t) starts /usr/sbin/httpd (labeled httpd_exec_t), and policy defines a transition into httpd_t. This is why a mislabeled executable (for example a binary copied to a non-standard location) often runs in the wrong domain or fails to transition.
Policy types¶
| Policy | Description |
|---|---|
targeted (default) |
Confines specific network-facing services and system daemons. Everything else runs unconfined_t. This is what RHEL uses and what the exams use. |
mls |
Multi-Level Security. Strict, used in specialized government/military environments. Ships separately (selinux-policy-mls). |
Modes¶
| Mode | Behavior |
|---|---|
| Enforcing | Policy is enforced; violations are denied and logged. |
| Permissive | Policy is not enforced; violations are only logged. Ideal for diagnosing whether SELinux is the cause. |
| Disabled | SELinux is not loaded at all. No labeling of new files occurs, so re-enabling requires a full relabel. |
2. Exam objectives (RHCSA / RHCE)¶
Red Hat revises exam objectives with each RHEL release. Always verify against the current objectives pages for EX200 (RHCSA) and EX294 (RHCE) for the exact release you are sitting. The list below reflects the SELinux-related material in the RHEL 9 era objectives.
RHCSA (EX200)¶
Under Manage security:
- Set enforcing and permissive modes for SELinux
- List and identify SELinux file and process context
- Restore default file contexts
- Use boolean settings to modify system SELinux settings
- Diagnose and address routine SELinux policy violations
SELinux also shows up implicitly in other objectives:
- Network services: configuring Apache, NFS, Samba, or SSH on non-default ports or directories requires port labels (
semanage port) and file contexts (semanage fcontext), usually together withfirewall-cmd. - Storage and file systems: new mount points, NFS/Samba shares, and web content directories need correct labels.
- Users and permissions: home directories and shared directories.
- Boot and troubleshooting: relabeling with
/.autorelabelafter recovery work.
RHCE (EX294) - Ansible automation¶
RHCE is Ansible-based, so SELinux is tested as an automation task. Expect to:
- Manage SELinux state and policy with
ansible.posix.selinux - Manage booleans with
ansible.posix.seboolean - Manage persistent file contexts with
community.general.sefcontext - Manage port labels with
community.general.seport - Restore contexts with
ansible.builtin.command: restorecon ...(oransible.builtin.filewithsetype, noting the caveat in section 15) - Use the RHEL System Roles
selinuxrole (rhel-system-roles.selinux) - Handle the reboot needed when moving from
disabledtoenforcing
Everything in RHCSA SELinux is a prerequisite: you must understand what the modules do under the hood.
Exam strategy¶
- Never disable SELinux. Leave it
enforcingand make it work. Disabling or leaving it permissive can cost you the entire objective. - Persistence is the trap.
chcon,setenforce, andsetseboolwithout-Pall vanish on reboot or relabel. The exam checks after a reboot. - Use the man pages.
man semanage-fcontext,man semanage-port, andman 8 httpd_selinux(or whatever service you are configuring) are available on the exam and contain examples. Learn theman -k _selinuxtrick (section 13). - Verify with
ls -Z,ps -eZ,getsebool, andsemanage ... -lafter every change, and finish with a reboot test. - Remember the firewall is a separate layer. A service on port 8888 needs both an SELinux port label and a firewalld rule.
3. Packages and binaries¶
| Package | Provides |
|---|---|
libselinux-utils |
getenforce, setenforce, getsebool, selinuxenabled, matchpathcon, avcstat, and other low-level utilities |
policycoreutils |
setsebool, restorecon, fixfiles, setfiles, load_policy, semodule |
policycoreutils-python-utils |
semanage, audit2allow, audit2why, sepolicy (formerly policycoreutils-python on RHEL 7 and earlier) |
setools-console |
seinfo, sesearch, sechecker |
setroubleshoot-server |
sealert and the setroubleshootd service for human-readable AVC analysis |
selinux-policy-targeted |
The targeted policy itself |
selinux-policy-doc |
Per-service man pages (*_selinux) |
selinux-policy-devel |
Policy development headers and sepolicy generate support |
udica |
Generates container-specific SELinux policies |
Install the working set:
dnf -y install policycoreutils policycoreutils-python-utils setools-console setroubleshoot-server
List binaries shipped in libselinux-utils:
rpm -ql libselinux-utils | grep bin
| Binary | Purpose |
|---|---|
getenforce |
Print current mode (Enforcing / Permissive / Disabled) |
setenforce |
Switch between enforcing (1) and permissive (0) at runtime |
selinuxenabled |
Exit status 0 if SELinux is enabled (useful in scripts) |
getsebool |
Read boolean values |
matchpathcon |
Show the default context for a path |
avcstat |
Access Vector Cache statistics |
selinuxconlist / selinuxdefcon |
List / show default contexts reachable for a user |
selinuxexeccon |
Show the context a program would run with when executed |
selabel_lookup, selabel_digest, selabel_partial_match, selabel_lookup_best_match |
Query the labeling database (debugging tools) |
selinux_restorecon |
Library-backed variant of the restorecon operation |
The three tools you will use constantly:
| Tool | Purpose | Persistent? |
|---|---|---|
chcon |
Change a file's context directly | No. Lost on relabel/restorecon. |
restorecon |
Reset a file's context to the policy default | Applies what the policy database says |
semanage |
Edit the policy database (file contexts, ports, booleans, users, logins, permissive domains) | Yes |
4. Managing SELinux status and modes¶
Check status¶
getenforce # Enforcing | Permissive | Disabled
sestatus # detailed status
sestatus -b # also list all booleans
selinuxenabled && echo on || echo off
Typical sestatus output:
SELinux status: enabled
SELinuxfs mount: /sys/fs/selinux
SELinux root directory: /etc/selinux
Loaded policy name: targeted
Current mode: enforcing
Mode from config file: enforcing
Policy MLS status: enabled
Policy deny_unknown status: allowed
Memory protection checking: actual (secure)
Max kernel policy version: 33
Note Current mode versus Mode from config file: they can differ after a setenforce.
Change mode at runtime (not persistent)¶
setenforce 0 # permissive (equivalently: setenforce Permissive)
setenforce 1 # enforcing (equivalently: setenforce Enforcing)
setenforce cannot be used when the system is disabled.
Change mode persistently¶
Edit /etc/selinux/config:
# SELINUX= can take one of these three values:
# enforcing - SELinux security policy is enforced.
# permissive - SELinux prints warnings instead of enforcing.
# disabled - No SELinux policy is loaded.
SELINUX=enforcing
# SELINUXTYPE= can take one of these values:
# targeted - Targeted processes are protected,
# mls - Multi Level Security protection.
SELINUXTYPE=targeted
The change takes effect at next boot. On modern RHEL /etc/sysconfig/selinux is a symlink to this file.
Kernel boot parameters (override the config file)¶
| Parameter | Effect |
|---|---|
enforcing=0 |
Boot in permissive for this boot only |
enforcing=1 |
Boot in enforcing for this boot only |
selinux=0 |
Disable SELinux for this boot |
autorelabel=1 |
Force a full filesystem relabel at boot |
Use these from the GRUB menu (press e, append to the linux line) for recovery. To apply persistently with grubby:
grubby --update-kernel=ALL --args="selinux=0" # disable (not recommended)
grubby --update-kernel=ALL --remove-args="selinux=0"
On RHEL 9 and later, the
SELINUX=disabledsetting still works, but kernel-parameter-based disabling is the supported path for full disablement, and runtime disabling by writing to/sys/fs/selinux/disablewas removed from the kernel. Prefer permissive over disabled if you only need to stop enforcement.
Making a single domain permissive¶
Instead of putting the whole system in permissive, relax just one domain while you build or debug policy:
semanage permissive -a httpd_t # httpd_t denials are logged but not blocked
semanage permissive -l # list permissive domains
semanage permissive -d httpd_t # remove
Disabling SELinux (last resort)¶
Temporary (permissive, not truly disabled):
setenforce 0
Permanent:
vi /etc/selinux/config # set SELINUX=disabled
systemctl reboot
Do not do this on a production server or on an exam. Diagnosing the denial is almost always faster than the fallout of disabling. See Stop disabling SELinux.
Re-enabling after disabled: relabel required¶
While disabled, the kernel does not label newly created files. Before returning to enforcing, force a full relabel or the system may fail to boot or services may break:
# 1. Set SELINUX=permissive in /etc/selinux/config first (safer than jumping straight to enforcing)
touch /.autorelabel
systemctl reboot
# 2. After the relabel completes, verify, then switch to enforcing
fixfiles -F onboot performs the same trigger. The relabel can take a long time on large filesystems.
5. Viewing contexts¶
The -Z option works across many tools:
ls -Z /var/www/html # files
ls -dZ /var/www/html # the directory itself
ps -eZ | grep httpd # processes
ps -auxZ # ps aux with context
id -Z # your own context
ss -tlnpZ # listening sockets with process context
cat /proc/self/attr/current # context of the current process
Examples:
$ id -Z
unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023
$ ps -eZ | grep httpd
system_u:system_r:httpd_t:s0 1234 ? 00:00:01 httpd
$ ls -Z /var/www/html/index.html
unconfined_u:object_r:httpd_sys_content_t:s0 /var/www/html/index.html
Find the default context for a path (what restorecon will set):
matchpathcon /var/www/html/index.html
matchpathcon -V /var/www/html/index.html # verify current vs default
semanage fcontext -l | grep '/var/www'
Common file types you should recognize¶
| Type | Used for |
|---|---|
httpd_sys_content_t |
Read-only web content |
httpd_sys_rw_content_t |
Web content the server may write (uploads, caches) |
httpd_log_t |
Apache logs |
httpd_sys_script_exec_t |
CGI scripts |
user_home_t |
Files in user home directories |
tmp_t, var_t, default_t |
Generic locations. default_t on a service data path is a classic sign of a missing fcontext rule. |
samba_share_t |
Samba-shared directories |
public_content_t / public_content_rw_t |
Content readable (or writable) by several services (ftp, httpd, nfs, samba, rsync) |
ssh_home_t |
~/.ssh contents |
container_file_t |
Files usable by containers |
etc_t, shadow_t, passwd_file_t |
Configuration and credentials |
6. File contexts¶
chcon: temporary changes¶
chcon writes the label straight onto the file (an extended attribute). It is convenient for testing but does not update the policy database, so a later restorecon, fixfiles, or full relabel reverts it.
chcon -t httpd_sys_content_t /srv/site/index.html
chcon -Rv --type=httpd_sys_content_t /srv/name.domain.site/
chcon --reference=/var/www/html /srv/site # copy a context from another file
semanage fcontext + restorecon: persistent changes (the correct way)¶
This is a two-step process:
semanage fcontextrecords a rule mapping a path regex to a context in the policy database.restoreconapplies the recorded rules to files on disk.
semanage fcontext -a -t httpd_sys_content_t "/web(/.*)?"
restorecon -Rv /web
The regex (/.*)? matches the directory itself and everything beneath it. Quote it so the shell does not interpret it.
semanage fcontext -l # all rules (very long)
semanage fcontext -l -C # only your local customizations
semanage fcontext -l | grep /var/www # search
semanage fcontext -m -t new_type "/web(/.*)?" # modify an existing rule
semanage fcontext -d "/web(/.*)?" # delete a rule
Local rules are stored in /etc/selinux/targeted/contexts/files/file_contexts.local.
Equivalence rules¶
To make a new tree behave exactly like a standard one (all sub-paths labeled the same way as the original):
semanage fcontext -a -e /var/www /web
restorecon -Rv /web
This is often the simplest exam answer for "make /web labeled like /var/www".
restorecon options¶
restorecon -v /path # restore one file, verbose (shows changes)
restorecon -Rv /path # recursive
restorecon -Rvn /path # dry run (-n): report what would change
restorecon -RvF /path # force: also reset the user and role fields
restorecon -Rv / # (avoid unless needed) whole tree
Full-system relabel options:
fixfiles -F relabel # relabel now (interactive prompts)
fixfiles -F onboot # schedule relabel at next boot
touch /.autorelabel && reboot # equivalent boot-time trigger
Copy vs move vs archive: how labels behave¶
This catches many people:
| Operation | Resulting label |
|---|---|
cp file /dest/ (new file) |
Inherits the destination directory's default context (correct) |
cp file /dest/existing (overwrite) |
Keeps the existing destination file's context |
mv file /dest/ |
Keeps the source label, so it is often wrong for the new location |
cp -a / cp --preserve=context |
Preserves the source label |
tar / rsync -a |
Depends on options (--selinux for tar, -X/--xattrs for rsync) |
After mv-ing web content into /var/www/html, run restorecon -Rv /var/www/html.
Reading the effect: default_t and other tells¶
When newly created top-level directories such as /web or /data are labeled default_t, most services are denied. Always add an fcontext rule and restore.
7. Booleans¶
Booleans are on/off switches that toggle predefined chunks of policy without writing new rules. They are the first thing to check when a confined service is denied a behavior (as opposed to a mislabeled file).
Viewing¶
getsebool -a # all booleans
getsebool -a | grep httpd # filter
getsebool httpd_can_network_connect # one boolean
semanage boolean -l # with descriptions and default vs current state
semanage boolean -l -C # only locally modified booleans
sestatus -b # via sestatus
semanage boolean -l output columns: name, (current, default) state, description.
Setting¶
setsebool httpd_can_network_connect on # runtime only, lost at reboot
setsebool -P httpd_can_network_connect on # -P makes it persistent
setsebool -P httpd_can_network_connect 1 # 1/0 or on/off are equivalent
setsebool -P httpd_can_network_connect=1 httpd_enable_homedirs=1 # several at once
Always use -P unless you specifically want a temporary change. -P rebuilds part of the policy store and may take a few seconds.
Commonly needed booleans¶
| Boolean | Effect |
|---|---|
httpd_can_network_connect |
Allow httpd scripts/modules to make outbound network connections (reverse proxy to app servers, Redis, APIs) |
httpd_can_network_connect_db |
Allow httpd to connect to database ports (MySQL/PostgreSQL) |
httpd_can_sendmail |
Allow httpd to send mail |
httpd_enable_homedirs |
Allow httpd to serve ~user/public_html |
httpd_read_user_content |
Allow httpd to read user content |
httpd_unified |
Treat all httpd types as one (loosens separation) |
httpd_use_nfs / httpd_use_cifs |
Serve content from NFS/CIFS mounts |
samba_enable_home_dirs |
Share home directories over Samba |
samba_export_all_rw / samba_export_all_ro |
Share any file read-write / read-only |
nfs_export_all_rw / nfs_export_all_ro |
Same for NFS |
use_nfs_home_dirs |
Use NFS-mounted home directories |
ftpd_anon_write / ftpd_full_access |
FTP write/full access |
ssh_sysadm_login |
Allow admin users to log in over SSH with the sysadm_r role |
container_manage_cgroup |
Allow containers to manage cgroups (needed for systemd in containers) |
virt_use_nfs |
Allow VMs to use NFS storage |
Find booleans for a service with:
getsebool -a | grep -i <service>
man <service>_selinux
sesearch --bool <boolean> # see what rules a boolean controls
8. Port labels¶
Confined daemons may only bind to or connect to ports labeled with a type they are permitted to use. Running httpd on 8888 or SSH on 2222 requires labeling the port.
List¶
semanage port -l # all
semanage port -l -C # local customizations only
semanage port -l | grep -i http # find http-related labels
semanage port -l | grep -w http_port_t
Example output:
http_cache_port_t tcp 8080, 8118, 8123, 10001-10010
http_cache_port_t udp 3130
http_port_t tcp 80, 81, 443, 488, 8008, 8009, 8443, 9000
pegasus_http_port_t tcp 5988
pegasus_https_port_t tcp 5989
Add, modify, delete¶
semanage port -a -t http_port_t -p tcp 8888 # add
semanage port -m -t http_port_t -p tcp 9999 # modify (port already has a different label)
semanage port -d -t http_port_t -p tcp 8888 # delete local rule
Ports defined in the base policy cannot be deleted, only overridden with -m. If -a complains the port is already defined, use -m.
Pair with the firewall¶
firewall-cmd --permanent --add-port=8888/tcp
firewall-cmd --reload
Example: SSH on port 2222¶
semanage port -a -t ssh_port_t -p tcp 2222
# edit /etc/ssh/sshd_config: Port 2222
firewall-cmd --permanent --add-port=2222/tcp && firewall-cmd --reload
systemctl restart sshd
Inspect the policy for port types¶
seinfo --portcon=443 --protocol=tcp
sepolicy network -t http_port_t
9. Worked example: Apache¶
Standard content: nothing to do¶
Content in /var/www/html is already httpd_sys_content_t.
Non-standard document root¶
Serve a site from /srv/name.domain.site/:
# Persistent read-only content
semanage fcontext -a -t httpd_sys_content_t "/srv/name.domain.site(/.*)?"
restorecon -Rv /srv/name.domain.site
# Writable subdirectory (uploads/cache/temp)
semanage fcontext -a -t httpd_sys_rw_content_t "/srv/name.domain.site/uploads(/.*)?"
restorecon -Rv /srv/name.domain.site/uploads
# Logs belong to httpd_log_t, not the rw content type
semanage fcontext -a -t httpd_log_t "/srv/name.domain.site/log(/.*)?"
restorecon -Rv /srv/name.domain.site/log
Or label it like the stock web root:
semanage fcontext -a -e /var/www /srv/name.domain.site
restorecon -Rv /srv/name.domain.site
Another common case, serving a kickstart repository over HTTP:
semanage fcontext -a -t httpd_sys_content_t "/backup/repo/kickstart(/.*)?"
restorecon -Rv /backup/repo/kickstart
(chcon -Rv --type=httpd_sys_content_t /backup/repo/kickstart/ also works immediately but will not survive a relabel.)
Non-standard port¶
semanage port -a -t http_port_t -p tcp 8888
firewall-cmd --permanent --add-port=8888/tcp && firewall-cmd --reload
Outbound connections¶
# Database (MySQL/PostgreSQL) from web application
setsebool -P httpd_can_network_connect_db 1
# General outbound connections: reverse proxy, Redis, external APIs
setsebool -P httpd_can_network_connect 1
httpd_can_network_connect is broad. For a single backend service you can sometimes get a tighter result by labeling the target port with a type httpd may connect to, but for the exam and most deployments the boolean is the expected answer.
Other helpful Apache booleans¶
setsebool -P httpd_enable_homedirs 1 # ~user directories
setsebool -P httpd_can_sendmail 1
Also see man httpd_selinux (from selinux-policy-doc) for every type and boolean related to Apache.
Verify¶
ls -Z /srv/name.domain.site
ps -eZ | grep httpd
getsebool httpd_can_network_connect_db
curl -I http://localhost:8888/
ausearch -m avc -ts recent # should be empty
10. Troubleshooting denials¶
Recommended workflow¶
- Confirm SELinux is the cause. Temporarily switch to permissive (
setenforce 0) and retest. If the problem persists, it is not SELinux. Switch back withsetenforce 1immediately after. Alternatively, usesemanage permissive -a <domain>to relax only one domain. - Read the denial (
ausearch,sealert). - Identify the category of problem (see table below).
- Apply the narrowest fix: relabel, boolean, or port label first; custom module last.
- Verify in enforcing mode and check for new AVCs.
Where denials are logged¶
| Source | Notes |
|---|---|
/var/log/audit/audit.log |
Primary location when auditd is running. Lines start with type=AVC. |
journalctl / kernel ring buffer (dmesg) |
Fallback when auditd is not running |
journalctl -t setroubleshoot |
Human-readable summaries from setroubleshootd |
Reading raw AVC messages¶
ausearch -m avc,user_avc,selinux_err -ts recent # last 10 minutes
ausearch -m avc -ts today
ausearch -m avc -c httpd # by command name
ausearch -m avc -ts boot -i # -i interprets numeric fields
grep AVC /var/log/audit/audit.log | tail
aureport -a # summary report of AVC denials
Example AVC:
type=AVC msg=audit(1726790400.123:456): avc: denied { read } for pid=1234
comm="httpd" name="index.html" dev="dm-0" ino=5678
scontext=system_u:system_r:httpd_t:s0
tcontext=unconfined_u:object_r:default_t:s0 tclass=file permissive=0
How to read it:
| Field | Value here | Meaning |
|---|---|---|
denied { read } |
read | The operation that was blocked |
comm |
httpd | The command |
scontext |
httpd_t |
Source domain (the process) |
tcontext |
default_t |
Target type (the file) |
tclass |
file | Object class |
permissive=0 |
0 | The action was actually blocked (1 means it was only logged) |
A source domain of httpd_t reading a target of default_t is a mislabel problem: fix with semanage fcontext + restorecon.
sealert and setroubleshoot¶
dnf -y install setroubleshoot-server
sealert -a /var/log/audit/audit.log # analyze the whole audit log
sealert -l <UUID> # details on one alert
journalctl -t setroubleshoot # recent alerts as one-line messages
sealert gives a plain-English explanation and proposes remedies, ranked by confidence: often a restorecon, a boolean, or an semanage command. Read the suggestion critically; do not blindly paste the audit2allow fallback.
Common denial patterns and fixes¶
| Symptom | Likely cause | Fix |
|---|---|---|
Service cannot read files in a custom directory; tcontext is default_t, var_t, user_home_t, or tmp_t |
Wrong file type | semanage fcontext -a -t <type> "<path>(/.*)?" then restorecon -Rv <path> |
Content moved with mv is denied |
Label followed the file | restorecon -Rv <dest> |
Service cannot bind a port (name_bind denied) |
Port not labeled for the service | semanage port -a -t <type> -p tcp <port> |
Service cannot connect out (name_connect denied) |
Boolean off, or destination port type not allowed | setsebool -P <boolean> on |
| Service works in permissive only, no obvious boolean | Missing policy | Custom module with audit2allow (below) or report a bug |
| Everything broke after re-enabling SELinux | Files created while disabled are unlabeled | touch /.autorelabel && reboot |
A binary at a custom path runs under unconfined_service_t or fails to transition |
Wrong exec label | Label the binary (bin_t, <service>_exec_t) via fcontext and restorecon |
audit2allow: custom policy modules (last resort)¶
Use only when you have ruled out labels, booleans, and ports, and you understand what you are allowing.
# Explain why each denial happened and which boolean, if any, would allow it
ausearch -m avc -ts recent | audit2why
# Generate a local policy module for the denied domain
ausearch -c 'myapp' --raw | audit2allow -M myapp_local
cat myapp_local.te # ALWAYS review the rules
semodule -i myapp_local.pp # install (persistent)
semodule -l | grep myapp_local # list installed modules
semodule -r myapp_local # remove
Prefer -M module names that identify the application, and keep the .te file under version control. Do not run audit2allow against the entire audit log; you will allow things that should be denied.
dontaudit rules¶
Policy suppresses some denials that are known to be harmless (dontaudit). If you are hunting a mystery denial that never appears in the log, temporarily disable them:
semodule -DB # disable dontaudit rules and rebuild
# ...reproduce the problem, read the AVCs...
semodule -B # re-enable dontaudit and rebuild
11. SELinux users, roles, and confined users¶
Under the targeted policy, most Linux users map to unconfined_u and run as unconfined_t, so they are not restricted by SELinux beyond standard system protections. You can confine specific users:
semanage login -l # Linux login -> SELinux user mappings
semanage user -l # SELinux users and their allowed roles
seinfo -u # SELinux users defined in policy
Typical SELinux users:
| SELinux user | Purpose |
|---|---|
unconfined_u |
Default for regular users on targeted policy |
user_u |
Confined: no su/sudo, no setuid apps that change identity |
staff_u |
Confined but allowed to use sudo to switch to admin roles |
sysadm_u |
Administrator role |
guest_u / xguest_u |
Highly restricted (no network / browser-only kiosk) |
root |
Mapped to unconfined_u by default |
system_u |
For system processes and objects |
Map a Linux user to a confined SELinux user:
semanage login -a -s user_u alice
semanage login -m -s staff_u alice # modify
semanage login -d alice # remove (falls back to __default__)
The user must log out and back in for the new mapping to apply, and their home directory may need restorecon -RFv /home/alice.
12. Containers and SELinux¶
Container runtimes (Podman, Docker, CRI-O) run containers in the container_t domain, and each container gets a unique MCS category pair (for example s0:c123,c456) so one container cannot read another container's files.
Bind-mounting a host directory into a container fails unless the directory has a label the container may use. Podman can relabel for you:
podman run -v /srv/data:/data:Z image # private label (single container, unique MCS categories)
podman run -v /srv/data:/data:z image # shared label (multiple containers may use it)
| Option | Sets | Use when |
|---|---|---|
:Z |
container_file_t with this container's MCS categories |
Only one container uses the directory |
:z |
container_file_t with no categories |
Several containers share it |
Never use
:Z/:zon system directories such as/home,/etc,/usr, or/var. It rewrites their labels recursively and can break the host.
Related items:
ls -Z /srv/data
ps -eZ | grep container
setsebool -P container_manage_cgroup on # needed for systemd inside containers
udica -j container.json my_container # generate a tailored policy from `podman inspect` output
podman run --security-opt label=disable ... # disable confinement for one container (avoid)
podman run --security-opt label=type=my_container.process ... # use a custom type
For Kubernetes on RHEL-family nodes, SELinux is enabled at the node level; volume labeling is controlled through the pod's securityContext.seLinuxOptions.
13. Inspecting and extending policy¶
Query the policy (setools-console)¶
seinfo -t httpd_sys_content_t # details on a type (note the space after -t)
seinfo -t | grep httpd | head # list types
seinfo -b | grep httpd # list booleans
seinfo -u # SELinux users
seinfo --portcon=443 --protocol=tcp # port context for a port
sesearch --allow -s httpd_t -t httpd_sys_content_t # what may httpd_t do to this type?
sesearch --allow -b httpd_can_connect_ldap # rules controlled by a boolean
sesearch --dontaudit -s httpd_t # silenced denials
Documentation packages and man pages¶
dnf -y install selinux-policy-doc selinux-policy-devel policycoreutils-python-utils
mandb # rebuild the man database
man -k _selinux # list all service SELinux man pages
man httpd_selinux # types, booleans, ports for httpd (`man 8 httpd_selinux`)
# Generate a man page for any domain
sepolicy manpage -d httpd_t
man ./httpd_selinux.8 # view the generated file (path as printed by the command)
man sepolicy-manpage # documentation for the tool itself
sepolicy sub-commands worth knowing:
sepolicy booleans -b httpd_can_network_connect
sepolicy network -t http_port_t
sepolicy communicate -s httpd_t -t mysqld_t # can these domains talk?
sepolicy transition -s init_t -t httpd_t # how does a domain transition happen?
Policy module management¶
semodule -l # list installed modules
semodule -i module.pp # install/update
semodule -r module # remove
semodule -d module # disable a module
semodule -B # rebuild policy
semanage export -f local.txt # export local customizations
semanage import -f local.txt # import on another host
Where things live: /etc/selinux/config, /etc/selinux/targeted/ (policy store and contexts), /sys/fs/selinux/ (selinuxfs), /var/log/audit/audit.log.
14. auditd vs SELinux¶
They are often confused because SELinux denials end up in the audit log.
| SELinux | auditd | |
|---|---|---|
| Role | Enforcement: allows or blocks access | Recording: logs events; never blocks anything |
| Layer | Kernel LSM | Kernel audit subsystem plus userspace daemon |
| Configuration | Policy, labels, booleans | Audit rules (/etc/audit/rules.d/*.rules, loaded via augenrules) |
| Output | AVC denials as audit records | /var/log/audit/audit.log (all event types) |
| Question answered | "Is this process allowed to do this?" | "What happened, who did it, when?" |
They are complementary: SELinux generates AVC records, and auditd stores them. Without auditd running, AVCs are only in the kernel log and journalctl. Audit also records things SELinux does not care about (logins, sudo use, file changes, syscalls) and is required for many compliance standards (PCI-DSS, STIG, CIS).
Essential auditd commands¶
systemctl status auditd
auditctl -l # loaded rules
auditctl -s # status
auditctl -w /etc/passwd -p wa -k passwd_change # watch writes/attribute changes (runtime)
ausearch -k passwd_change -i # search by key
ausearch -m avc -ts today
ausearch -ua alice -ts yesterday -i # events by user
aureport --summary
aureport -a # AVC report
aureport -au --failed # failed authentications
Make rules persistent by placing them in /etc/audit/rules.d/, then augenrules --load.
See also: TrustedSec: SELinux and auditd.
15. Automating SELinux with Ansible (RHCE)¶
Modules come from the ansible.posix and community.general collections.
State and policy¶
- name: Ensure SELinux is enforcing with the targeted policy
ansible.posix.selinux:
policy: targeted
state: enforcing
register: selinux_result
- name: Reboot if SELinux state changed from disabled
ansible.builtin.reboot:
when: selinux_result.reboot_required
ansible.posix.selinux edits /etc/selinux/config and switches the runtime mode where possible. Going from disabled to enforcing/permissive requires a reboot (reboot_required: true), and you should schedule a relabel (/.autorelabel) as well.
Booleans¶
- name: Allow httpd to connect to databases
ansible.posix.seboolean:
name: httpd_can_network_connect_db
state: true
persistent: true # equivalent to setsebool -P
Forgetting persistent: true is the same mistake as forgetting -P.
File contexts¶
- name: Add persistent file context rule for the web root
community.general.sefcontext:
target: '/web(/.*)?'
setype: httpd_sys_content_t
state: present
- name: Apply the file context
ansible.builtin.command: restorecon -Rv /web
register: restorecon_out
changed_when: restorecon_out.stdout != ""
sefcontext only updates the policy database, so you still need restorecon. It can be run via the command module, or by using ansible.builtin.file with setype and recurse: true (note: file with setype behaves like chcon: it sets the label on disk but does not write a policy rule, so use it only together with sefcontext, or for temporary labels).
Alternative that avoids command: the selinux system role's selinux_restore_dirs (below).
Ports¶
- name: Label TCP 8888 as an HTTP port
community.general.seport:
ports: 8888
proto: tcp
setype: http_port_t
state: present
Permissive domains¶
- name: Make httpd_t permissive
community.general.selinux_permissive:
name: httpd_t
permissive: true
RHEL System Roles: the selinux role¶
Install with dnf install rhel-system-roles and reference the role as rhel-system-roles.selinux (or redhat.rhel_system_roles.selinux when using the collection form). One play can set everything:
- name: Configure SELinux
hosts: webservers
become: true
vars:
selinux_policy: targeted
selinux_state: enforcing
selinux_booleans:
- { name: httpd_can_network_connect_db, state: on, persistent: yes }
selinux_fcontexts:
- { target: '/web(/.*)?', setype: 'httpd_sys_content_t', state: present }
selinux_ports:
- { ports: 8888, proto: tcp, setype: 'http_port_t', state: present }
selinux_restore_dirs:
- /web
roles:
- rhel-system-roles.selinux
The role also supports selinux_logins, selinux_modules, and selinux_all_purge. The role will reboot-flag when a disabled system needs to be rebooted, so handle that in your play.
Exam tips for RHCE¶
- Use
ansible-doc ansible.posix.seboolean(and the others) to get syntax and examples during the exam; do not rely on memory. - Use
ansible-doc -l | grep -i selinuxto discover available modules. - Test idempotency: run the playbook twice; the second run should report
changed=0. - Verify on the managed node after the run:
ls -Zd /web,getsebool httpd_can_network_connect_db,semanage port -l | grep 8888.
16. Practice scenarios¶
Work through these on a lab VM. Try each without looking at the solution.
1. Make SELinux enforcing persistently, and confirm.
setenforce 1
sed -i 's/^SELINUX=.*/SELINUX=enforcing/' /etc/selinux/config
getenforce; grep ^SELINUX= /etc/selinux/config
2. Apache must serve /web/index.html (a new directory).
mkdir -p /web && echo hello > /web/index.html
# In httpd.conf, DocumentRoot "/web" and matching <Directory>
semanage fcontext -a -t httpd_sys_content_t "/web(/.*)?"
restorecon -Rv /web
systemctl restart httpd
curl http://localhost/
3. Apache must listen on 82 and 8888.
semanage port -l | grep -w http_port_t # 81 is already present; 82 is not
semanage port -a -t http_port_t -p tcp 82
semanage port -a -t http_port_t -p tcp 8888
firewall-cmd --permanent --add-port={82,8888}/tcp && firewall-cmd --reload
4. A PHP app must connect to a remote MariaDB.
setsebool -P httpd_can_network_connect_db 1
5. A file moved from /tmp to /var/www/html is denied.
ls -Z /var/www/html/file # shows tmp_t
restorecon -v /var/www/html/file
6. Share /srv/samba/public over Samba read-write.
semanage fcontext -a -t samba_share_t "/srv/samba/public(/.*)?"
restorecon -Rv /srv/samba/public
setsebool -P samba_export_all_rw 1 # only if you are not using samba_share_t; prefer the label
7. Move sshd to port 2222. (see section 8)
8. Diagnose an unknown denial.
ausearch -m avc -ts recent | audit2why
sealert -a /var/log/audit/audit.log
# apply the recommended restorecon / boolean / port fix
9. Recover after accidentally running with SELINUX=disabled.
sed -i 's/^SELINUX=.*/SELINUX=permissive/' /etc/selinux/config
touch /.autorelabel
reboot
# after relabel: set enforcing, reboot again
10. Confirm all changes survive a reboot: reboot, then re-run getenforce, getsebool, ls -Z, semanage port -l -C, semanage fcontext -l -C, semanage boolean -l -C.
17. Common pitfalls¶
- Using
chconand thinking you are done. It does not persist. Usesemanage fcontext+restorecon. - Forgetting
-Ponsetsebool. Works until the next reboot. - Using the wrong boolean syntax. It is
setsebool -P name 1(orname=1); a baresetsebool name=1without-Pis runtime-only. - Forgetting the
(/.*)?in fcontext regexes, so only the directory, not its contents, is labeled. - Forgetting to quote the regex so the shell mangles it.
mvpreserving old labels. Runrestoreconon the destination.- Relabeling with
chcon -Ron a huge tree when onerestorecon -Rwould have done it correctly. - Ignoring the firewall. A perfectly labeled port with no firewall rule still cannot be reached from outside.
- Skipping
ls -dZon the directory itself. The parent directory's label matters for traversal, not only the files. - Leaving the system permissive after diagnostics. Always run
getenforceat the end. - Running
audit2allowon everything. This creates overly broad policy and hides real misconfigurations. - Trusting old guidance. Many search results still mention
yum,policycoreutils-python, and RHEL 6 paths. On RHEL 8/9/10 usednfandpolicycoreutils-python-utils. - Home directories, NFS, and Samba each have their own booleans; check
getsebool -a | grep <service>before writing a module.
18. References¶
- Red Hat: What is SELinux?
- Wikipedia: Security-Enhanced Linux
- Red Hat Enterprise Linux documentation: Using SELinux (choose your RHEL major version at docs.redhat.com); includes the chapters on persistent file-context changes with
semanage fcontextand on allowing access withaudit2allow - Tag1 Consulting: Stop disabling SELinux
- TrustedSec: SELinux and auditd
- Local man pages:
selinux(8),semanage(8),semanage-fcontext(8),semanage-port(8),semanage-boolean(8),restorecon(8),setsebool(8),audit2allow(1),sealert(8),sepolicy(8),ausearch(8), and the per-service*_selinux(8)pages - Red Hat exam pages: RHCSA (EX200) and RHCE (EX294) objectives on redhat.com/en/services/training