SELinux (Security-Enhanced Linux) enforces Mandatory Access Control (MAC) on RHEL 9, confining processes to only the resources they are explicitly permitted to access regardless of traditional Unix file permissions. While RHEL ships with comprehensive base policies, custom applications often generate AVC (Access Vector Cache) denials because their access patterns are not covered by existing policy modules. Rather than disabling SELinux enforcement, this tutorial shows you how to generate custom policy modules from real denials, compile and load them, and use semanage to apply persistent file context labels. By the end you will be able to address any SELinux denial without weakening the overall MAC posture.
Prerequisites
- RHEL 9 with SELinux in enforcing or permissive mode (
getenforceto check) - The
policycoreutils-python-utilsandsetools-consolepackages installed - An application generating AVC denials (or the ability to reproduce them in permissive mode)
- Basic familiarity with reading system logs
Step 1 — Install Required Policy Tools
Install the development tools and utilities needed to write, compile, and load custom SELinux policy modules:
dnf install -y policycoreutils policycoreutils-python-utils
setools-console selinux-policy-devel
setroubleshoot-server audit
# Ensure auditd is running (captures AVC messages)
systemctl enable --now auditd
# Confirm SELinux is active
getenforce
sestatus
Step 2 — Inspect Process Domains and File Contexts
Every process runs in a security domain and every file has a type label. Understanding these is the foundation of writing policy:
# View the SELinux domain for all running processes
ps -eZ | grep -i httpd # example: confined_web_t, httpd_t
# View the SELinux context (type label) for files
ls -lZ /var/www/html/
ls -lZ /srv/myapp/
# Check the context of the current shell process
id -Z
# Find all files with a specific SELinux type
find /srv/myapp -context '*httpd_content_t*' -print
Step 3 — Generate a Custom Policy Module from AVC Denials
When an application is denied, the audit log records an AVC message. Use ausearch to collect recent denials and pipe them through audit2allow to generate a policy module automatically:
# View recent AVC denials
ausearch -m AVC,USER_AVC -ts recent
# Generate a policy module named "mycustompolicy"
ausearch -m AVC,USER_AVC -ts recent | audit2allow -M mycustompolicy
# This creates two files:
# mycustompolicy.te — human-readable Type Enforcement source
# mycustompolicy.pp — compiled policy package
# Review the generated .te file to understand what was allowed
cat mycustompolicy.te
The .te file contains allow rules such as allow myapp_t httpd_content_t:file { read open };. Read and understand each rule before loading it — never blindly load auto-generated policy without review.
Step 4 — Edit, Compile, and Load the Policy Module
After reviewing the generated .te file, compile it against the installed policy headers and load it into the running kernel policy store:
# (Optional) Edit the .te file to tighten or correct rules
# Example .te structure:
# module mycustompolicy 1.0;
# require { type myapp_t; type httpd_content_t; class file { read open }; }
# allow myapp_t httpd_content_t:file { read open };
# Compile the module using the SELinux development Makefile
make -f /usr/share/selinux/devel/Makefile mycustompolicy.pp
# Load the compiled module
semodule -i mycustompolicy.pp
# Verify it is loaded
semodule -l | grep mycustompolicy
# Confirm the denial is resolved by retesting the application
# Check that no new AVCs appear:
ausearch -m AVC -ts recent | tail -20
Step 5 — Apply Persistent File Context Labels with semanage
If your application serves files from a non-standard path (e.g., /srv/myapp instead of /var/www/html), add a persistent file context mapping so the correct label is applied after relabeling:
# Add a file context mapping for /srv/myapp and all files beneath it
semanage fcontext -a -t httpd_content_t '/srv/myapp(/.*)?'
# Apply the new labels to existing files
restorecon -Rv /srv/myapp
# Verify the labels are correct
ls -lZ /srv/myapp/
# List all custom fcontext records you have added
semanage fcontext -l | grep myapp
# To allow an SELinux boolean (e.g., allow httpd to serve user home dirs)
setsebool -P httpd_enable_homedirs on
getsebool httpd_enable_homedirs
Step 6 — Troubleshoot with sealert
sealert from the setroubleshoot-server package provides human-readable explanations of AVC denials with suggested remediation steps:
# Analyse all recent AVC messages and print explanations
sealert -a /var/log/audit/audit.log
# If setroubleshoot is running as a service, check its output
journalctl -u setroubleshootd -n 50
# Run the application in permissive mode for a single domain to collect
# all denials at once without blocking (useful during initial testing)
semanage permissive -a myapp_t
# ... reproduce all application actions ...
semanage permissive -d myapp_t # restore to enforcing for that domain
Conclusion
You have learned how to diagnose SELinux AVC denials, generate targeted policy modules with audit2allow, compile and load them, and apply persistent file context labels with semanage. This workflow lets you run custom applications under full SELinux enforcement without weakening the system-wide MAC posture. Always review auto-generated policy before loading it, and prefer tightly scoped rules over broad unconfined transitions.
Next steps: How to Set Up a Certificate Authority with OpenSSL on RHEL 9, How to Write SELinux Type Enforcement Policies from Scratch on RHEL 9, and How to Audit SELinux Policy with seinfo and sesearch on RHEL 9.