Andrew Mercer
on this page

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

  1. Concepts
  2. Exam objectives (RHCSA / RHCE)
  3. Packages and binaries
  4. Managing SELinux status and modes
  5. Viewing contexts
  6. File contexts: chcon, semanage fcontext, restorecon
  7. Booleans
  8. Port labels
  9. Worked example: Apache
  10. Troubleshooting denials
  11. SELinux users, roles, and confined users
  12. Containers and SELinux
  13. Inspecting and extending policy
  14. auditd vs SELinux
  15. Automating SELinux with Ansible (RHCE)
  16. Practice scenarios
  17. Common pitfalls
  18. 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 with firewall-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 /.autorelabel after 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 ... (or ansible.builtin.file with setype, noting the caveat in section 15)
  • Use the RHEL System Roles selinux role (rhel-system-roles.selinux)
  • Handle the reboot needed when moving from disabled to enforcing

Everything in RHCSA SELinux is a prerequisite: you must understand what the modules do under the hood.

Exam strategy

  • Never disable SELinux. Leave it enforcing and make it work. Disabling or leaving it permissive can cost you the entire objective.
  • Persistence is the trap. chcon, setenforce, and setsebool without -P all vanish on reboot or relabel. The exam checks after a reboot.
  • Use the man pages. man semanage-fcontext, man semanage-port, and man 8 httpd_selinux (or whatever service you are configuring) are available on the exam and contain examples. Learn the man -k _selinux trick (section 13).
  • Verify with ls -Z, ps -eZ, getsebool, and semanage ... -l after 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=disabled setting still works, but kernel-parameter-based disabling is the supported path for full disablement, and runtime disabling by writing to /sys/fs/selinux/disable was 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:

  1. semanage fcontext records a rule mapping a path regex to a context in the policy database.
  2. restorecon applies 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

  1. Confirm SELinux is the cause. Temporarily switch to permissive (setenforce 0) and retest. If the problem persists, it is not SELinux. Switch back with setenforce 1 immediately after. Alternatively, use semanage permissive -a <domain> to relax only one domain.
  2. Read the denial (ausearch, sealert).
  3. Identify the category of problem (see table below).
  4. Apply the narrowest fix: relabel, boolean, or port label first; custom module last.
  5. 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/:z on 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 selinux to 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 chcon and thinking you are done. It does not persist. Use semanage fcontext + restorecon.
  • Forgetting -P on setsebool. Works until the next reboot.
  • Using the wrong boolean syntax. It is setsebool -P name 1 (or name=1); a bare setsebool name=1 without -P is 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.
  • mv preserving old labels. Run restorecon on the destination.
  • Relabeling with chcon -R on a huge tree when one restorecon -R would have done it correctly.
  • Ignoring the firewall. A perfectly labeled port with no firewall rule still cannot be reached from outside.
  • Skipping ls -dZ on the directory itself. The parent directory's label matters for traversal, not only the files.
  • Leaving the system permissive after diagnostics. Always run getenforce at the end.
  • Running audit2allow on 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 use dnf and policycoreutils-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 fcontext and on allowing access with audit2allow
  • 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