🔧 SELinux Troubleshooting

SELinux Series: Part 1 — Basics  |  Part 2 — Contexts  |  Part 3 — Booleans  |  Part 4 — Troubleshooting

The Troubleshooting Mindset

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.

The Standard Troubleshooting Workflow

  1. Something fails. Suspect SELinux — set Permissive temporarily to confirm: sudo setenforce 0
  2. If it works in Permissive, SELinux is the cause. Set back to Enforcing: sudo setenforce 1
  3. Find the AVC denial: sudo ausearch -m avc -ts recent
  4. Get a plain-English explanation: sudo ausearch -m avc -ts recent | audit2why
  5. Apply the fix — boolean, restorecon, semanage fcontext, or audit2allow module
  6. Test in Enforcing mode. Verify the fix holds.
  7. Document the change.

Examples

1
Find Denials — ausearch

ausearch 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 | audit2why
Raw AVC denial:
time->Tue Apr 14 10:22:31 2026 type=AVC msg=audit(1712345678.123:456): avc: denied { read } for pid=1234 comm="httpd" name="myapp.conf" dev="sda1" ino=987654 scontext=system_u:system_r:httpd_t:s0 tcontext=unconfined_u:object_r:admin_home_t:s0 tclass=file permissive=0
💡 Reading the raw 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.
2
Explain Denials — audit2why

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.log
audit2why output — boolean fix:
type=AVC msg=audit(1712345678.123:789): avc: denied { name_connect } for pid=1234 comm="httpd" ... Was caused by: The boolean httpd_can_network_connect was set incorrectly. Description: Allow httpd to make any network connection Allow access by executing: # setsebool -P httpd_can_network_connect 1
audit2why output — context fix:
type=AVC msg=audit(1712345678.123:456): avc: denied { read } for pid=1234 comm="httpd" name="myapp.conf" ... Was caused by: Incorrect file context Default security context differs from expected. Fix by executing: # restorecon -v /etc/myapp/myapp.conf
💡 audit2why tells you the exact fix command. Copy and run it. This is the fastest path from "SELinux blocked something" to "problem solved correctly." Install with policycoreutils-python-utils.
3
Human-Readable Analysis — sealert

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 -20
sealert output (abbreviated):
SELinux is preventing httpd from read access on the file myapp.conf. ***** Plugin restorecon (99.5 confidence) suggests ******************** If you want to fix the label, /etc/myapp/myapp.conf default label should be httpd_config_t. Then you can run restorecon. Do this by executing: # /sbin/restorecon -v /etc/myapp/myapp.conf ***** Plugin catchall (1.49 confidence) suggests ********************** If you believe that httpd should be allowed read access on the myapp.conf file by default, then you should report this as a bug. You can generate a local policy module to allow this access: # ausearch -c 'httpd' --raw | audit2allow -M my-httpd # semodule -X 300 -i my-httpd.pp Additional Information: Source Context: system_u:system_r:httpd_t:s0 Target Context: unconfined_u:object_r:admin_home_t:s0 Target Objects: myapp.conf [ file ]
Confidence scores: sealert ranks suggested fixes by confidence percentage. The highest confidence fix is almost always correct. A restorecon at 99.5% confidence means the file simply has the wrong label — fix it and move on.
4
Generate Custom Policy — audit2allow

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.log
my-httpd.te content:
module my-httpd 1.0; require { type httpd_t; type admin_home_t; class file read; } #============= httpd_t ============== allow httpd_t admin_home_t:file read;
⚠️ Always review before installing. Read the .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.
audit2allow is the last resort. The fix hierarchy is:
  1. Fix file context with restorecon or semanage fcontext
  2. Enable the right boolean with setsebool -P
  3. Add a port context with semanage port
  4. Generate a custom module with audit2allow only if none of the above apply
5
Query Policy — sesearch and seinfo

sesearch 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 http
sesearch -A -s httpd_t -c file -p read (partial):
allow httpd_t httpd_sys_content_t:file { read getattr open }; allow httpd_t httpd_config_t:file { read getattr open }; allow httpd_t httpd_log_t:file { read getattr open }; allow httpd_t cert_t:file { read getattr open }; allow httpd_t etc_t:file { read getattr open };
💡 sesearch for understanding, not just fixing. Before deploying a new application, use 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.
6
Common Denial Patterns and Fixes

The most frequently encountered SELinux denial scenarios and their solutions:

Pattern 1 — Wrong file context after copy

# Problem: file copied from home dir has wrong context
# Denial: httpd_t denied read on admin_home_t
sudo restorecon -Rv /var/www/html/

Pattern 2 — Service on non-standard port

# 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

Pattern 3 — Web app cannot connect to database

# 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

Pattern 4 — Custom application directory

# 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/

Pattern 5 — Service cannot write to log directory

# 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/

Pattern 6 — SSH key permissions issue

# Problem: SSH authorized_keys not working
# Check context — should be ssh_home_t
ls -Z ~/.ssh/authorized_keys
# Fix:
restorecon -Rv ~/.ssh/
7
Managing Policy Modules — semodule

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
Priority levels: Default system policy is priority 100. Use priority 300 for local customizations — this ensures your module takes precedence over the base policy without overwriting it. If a distribution update changes the base policy at 100, your customization at 300 still wins.
8
Complete Troubleshooting Script

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:
======================================== SELinux Diagnostic — web01.internal Tue Apr 14 11:45:00 EDT 2026 ======================================== --- MODE AND POLICY --- Enforcing SELinux status: enabled Current mode: enforcing Mode from config file: enforcing Loaded policy name: targeted --- RECENT DENIALS (last 10 min) --- Process: httpd --- PLAIN ENGLISH EXPLANATION --- Was caused by: The boolean httpd_can_network_connect_db was set incorrectly. Allow access by executing: # setsebool -P httpd_can_network_connect_db 1 --- NON-DEFAULT BOOLEANS --- httpd_can_sendmail (on , off) --- CUSTOM POLICY MODULES --- my-httpd --- PERMISSIVE DOMAINS --- None ========================================

Quick Reference

CommandWhat it does
ausearch -m avc -ts recentShow recent AVC denials
ausearch -m avc -ts bootAll denials since last boot
ausearch -m avc -c httpdDenials for specific process
ausearch -m avc -f filenameDenials involving specific file
audit2why -aExplain all denials in audit.log
ausearch ... | audit2whyExplain piped denials
sealert -a /var/log/audit/audit.logFull human-readable analysis
ausearch --raw | audit2allow -M mymodGenerate custom policy module
semodule -i mymod.ppInstall policy module
semodule -X 300 -i mymod.ppInstall with local priority
semodule -lList installed modules
semodule -r mymodRemove a module
sesearch -A -s httpd_t -c file -p readQuery what policy allows
seinfo -t | grep httpFind types matching a pattern

Fix Decision Tree

audit2why says...Fix
Incorrect file contextrestorecon -Rv /path/
Boolean set incorrectlysetsebool -P boolean_name on
Port not in typesemanage port -a -t type -p tcp PORT
Path not in policysemanage fcontext -a -t type "/path(/.*)?" + restorecon
No standard fix availableaudit2allow -M mymod + review + semodule -i

Essential Packages

PackageProvidesInstall
policycoreutilsausearch, audit2why, audit2allow, semoduleUsually pre-installed
policycoreutils-python-utilssemanage, audit2why (RHEL 8+)dnf install policycoreutils-python-utils
setroubleshoot-serversealert, setroubleshootddnf install setroubleshoot-server
setools-consolesesearch, seinfodnf install setools-console

← Back to SELinux Index ↑ Back to EXPANDED