SELinux problems always produce evidence — AVC denials in the audit log. The tools on this page read that evidence and tell you exactly what happened and how to fix it. The workflow is always the same: find the denial, understand it, apply the minimal fix.
The wrong response to an SELinux problem is
setenforce 0 or SELINUX=disabled. The right
response is to use ausearch, audit2why,
sealert, and audit2allow to understand the denial
and apply a targeted fix — a boolean, a context correction, or a custom
policy module.
Permissive temporarily to confirm: sudo setenforce 0sudo setenforce 1sudo ausearch -m avc -ts recentsudo ausearch -m avc -ts recent | audit2whyausearch queries the audit log for SELinux AVC denial events:
# Show recent denials (last 10 minutes) sudo ausearch -m avc -ts recent # Show all denials since last boot sudo ausearch -m avc -ts boot # Show denials in the last hour sudo ausearch -m avc -ts today # Show denials for a specific process/command sudo ausearch -m avc -c httpd sudo ausearch -m avc -c nginx sudo ausearch -m avc -c sshd # Show denials involving a specific file sudo ausearch -m avc -f myapp.conf # Show denials for a specific PID sudo ausearch -m avc -p 1234 # Show all AVC events since a specific time sudo ausearch -m avc -ts "04/14/2026 10:00:00" # Pipe to audit2why for explanation (covered in Example 2) sudo ausearch -m avc -ts recent | audit2whyRaw AVC denial:
{ read } = action denied,
comm="httpd" = process,
name="myapp.conf" = object,
scontext=...httpd_t = process type,
tcontext=...admin_home_t = file type.
httpd_t cannot read admin_home_t — fix the file context.
audit2why translates raw AVC denials into plain English
and tells you what fix to apply:
# Explain recent denials sudo ausearch -m avc -ts recent | audit2why # Explain all denials since boot sudo ausearch -m avc -ts boot | audit2why # Explain denials for a specific service sudo ausearch -m avc -ts recent -c httpd | audit2why # Read directly from audit log sudo audit2why -a # From a saved log file sudo audit2why -i /tmp/mydenials.logaudit2why output — boolean fix:
policycoreutils-python-utils.
sealert from the setroubleshoot-server package
gives the most detailed, human-readable analysis with multiple fix options
ranked by likelihood:
# Analyze the entire audit log sudo sealert -a /var/log/audit/audit.log # Analyze recent denials only (pipe from ausearch) sudo ausearch -m avc -ts recent -l | sudo sealert -a /dev/stdin # Look up a specific alert by UUID from journalctl sudo sealert -l 8f34a21c-1234-5678-abcd-ef1234567890 # Watch for new alerts live sudo journalctl -f -t setroubleshoot # Check /var/log/messages for setroubleshoot summaries sudo grep "sealert" /var/log/messages | tail -20sealert output (abbreviated):
restorecon at 99.5% confidence means the file simply has
the wrong label — fix it and move on.
When no boolean or context fix applies, audit2allow generates
a custom policy module that allows the denied action:
# Generate a policy module from recent denials sudo ausearch -c 'httpd' --raw | audit2allow -M my-httpd # This creates two files: # my-httpd.te — human-readable policy source # my-httpd.pp — compiled policy package # Review what the policy allows BEFORE installing cat my-httpd.te # Install the policy module sudo semodule -X 300 -i my-httpd.pp # Verify it is loaded sudo semodule -l | grep my-httpd # Remove the module if no longer needed sudo semodule -r my-httpd # Generate from a specific log file sudo audit2allow -M my-policy -i /tmp/denials.logmy-httpd.te content:
.te file before running semodule -i.
audit2allow allows everything that was denied — including denials
that were legitimate security blocks. A broad policy from a noisy
log can grant more access than you intend. Use targeted
ausearch -c processname to filter before generating.
restorecon or semanage fcontextsetsebool -Psemanage portaudit2allow only if none of the above applysesearch and seinfo let you query the loaded
policy directly — useful for understanding what is and isn't allowed:
# What can httpd_t read? sudo sesearch -A -s httpd_t -c file -p read # Can httpd_t connect to mysqld_port_t? sudo sesearch -A -s httpd_t -t mysqld_port_t -c tcp_socket # What types can write to var_log_t? sudo sesearch -A -t var_log_t -c file -p write # List all types in the policy sudo seinfo -t | head -30 # Count total types, roles, users sudo seinfo # Show attributes of a type sudo seinfo -t httpd_t -x # Find all types with 'http' in the name sudo seinfo -t | grep httpsesearch -A -s httpd_t -c file -p read (partial):
sesearch to
understand what the policy already allows for your service type.
You may find the access you need is already permitted — or identify
exactly what context your files need to be accessible.
The most frequently encountered SELinux denial scenarios and their solutions:
# Problem: file copied from home dir has wrong context # Denial: httpd_t denied read on admin_home_t sudo restorecon -Rv /var/www/html/
# Problem: nginx configured for port 8080, SELinux blocks bind # Denial: httpd_t denied name_bind on port 8080 sudo semanage port -a -t http_port_t -p tcp 8080
# Problem: PHP app gets connection refused to MySQL # Denial: httpd_t denied name_connect to mysqld_port_t sudo setsebool -P httpd_can_network_connect_db on
# Problem: app installed in /opt/myapp — wrong context # Denial: httpd_t denied read on default_t or unlabeled_t sudo semanage fcontext -a -t httpd_sys_content_t "/opt/myapp(/.*)?" sudo restorecon -Rv /opt/myapp/
# Problem: custom app cannot write logs to /var/log/myapp/ # Denial: myapp_t denied write on var_log_t sudo semanage fcontext -a -t var_log_t "/var/log/myapp(/.*)?" sudo restorecon -Rv /var/log/myapp/
# Problem: SSH authorized_keys not working # Check context — should be ssh_home_t ls -Z ~/.ssh/authorized_keys # Fix: restorecon -Rv ~/.ssh/
List, install, remove, and manage custom SELinux policy modules:
# List all installed policy modules sudo semodule -l # List with priority sudo semodule -lfull # Install a compiled module sudo semodule -i my-httpd.pp # Install with explicit priority (300 = local customization) sudo semodule -X 300 -i my-httpd.pp # Remove a module sudo semodule -r my-httpd # Disable a module without removing sudo semodule -d my-httpd # Re-enable a disabled module sudo semodule -e my-httpd # Rebuild and reload all policy (after manual .te edits) sudo semodule -B
A practical script for diagnosing SELinux issues on any system:
#!/bin/bash
# selinux-diag.sh — SELinux diagnostic snapshot
echo "========================================"
echo " SELinux Diagnostic — $(hostname)"
echo " $(date)"
echo "========================================"
echo ""
echo "--- MODE AND POLICY ---"
getenforce
sestatus | grep -E "status|mode|policy"
echo ""
echo "--- RECENT DENIALS (last 10 min) ---"
DENIALS=$(sudo ausearch -m avc -ts recent 2>/dev/null)
if [ -z "$DENIALS" ]; then
echo "No recent AVC denials found."
else
echo "$DENIALS" | grep "comm=" | \
awk -F'"' '{print " Process:", $2}' | sort -u
echo ""
echo "--- PLAIN ENGLISH EXPLANATION ---"
echo "$DENIALS" | sudo audit2why 2>/dev/null
fi
echo ""
echo "--- NON-DEFAULT BOOLEANS ---"
sudo semanage boolean -l -C 2>/dev/null | tail -n +2 | \
awk '{printf " %-45s %s\n", $1, $2}' || echo " None"
echo ""
echo "--- CUSTOM POLICY MODULES ---"
sudo semodule -l 2>/dev/null | grep -v "^base\|^system" | \
head -20 || echo " None"
echo ""
echo "--- PERMISSIVE DOMAINS ---"
sudo semanage permissive -l 2>/dev/null | \
grep -v "^Builtin\|^Custom\|^$" || echo " None"
echo ""
echo "========================================"
echo " Run: sudo sealert -a /var/log/audit/audit.log"
echo " for full human-readable analysis"
echo "========================================"
Example output:
| Command | What it does |
|---|---|
| ausearch -m avc -ts recent | Show recent AVC denials |
| ausearch -m avc -ts boot | All denials since last boot |
| ausearch -m avc -c httpd | Denials for specific process |
| ausearch -m avc -f filename | Denials involving specific file |
| audit2why -a | Explain all denials in audit.log |
| ausearch ... | audit2why | Explain piped denials |
| sealert -a /var/log/audit/audit.log | Full human-readable analysis |
| ausearch --raw | audit2allow -M mymod | Generate custom policy module |
| semodule -i mymod.pp | Install policy module |
| semodule -X 300 -i mymod.pp | Install with local priority |
| semodule -l | List installed modules |
| semodule -r mymod | Remove a module |
| sesearch -A -s httpd_t -c file -p read | Query what policy allows |
| seinfo -t | grep http | Find types matching a pattern |
| audit2why says... | Fix |
|---|---|
| Incorrect file context | restorecon -Rv /path/ |
| Boolean set incorrectly | setsebool -P boolean_name on |
| Port not in type | semanage port -a -t type -p tcp PORT |
| Path not in policy | semanage fcontext -a -t type "/path(/.*)?" + restorecon |
| No standard fix available | audit2allow -M mymod + review + semodule -i |
| Package | Provides | Install |
|---|---|---|
| policycoreutils | ausearch, audit2why, audit2allow, semodule | Usually pre-installed |
| policycoreutils-python-utils | semanage, audit2why (RHEL 8+) | dnf install policycoreutils-python-utils |
| setroubleshoot-server | sealert, setroubleshootd | dnf install setroubleshoot-server |
| setools-console | sesearch, seinfo | dnf install setools-console |