SELinux Permission Denied: Complete Troubleshooting Guide for AVC Denials
Fix SELinux permission denied (AVC denial) errors fast. Learn to diagnose with audit2why, fix file contexts with restorecon, and create custom policies.
- SELinux blocks actions via AVC denials logged to /var/log/audit/audit.log — always read the denial before changing anything
- The most common root causes are incorrect file security contexts, missing booleans, and unlabeled ports
- Quick fixes: run restorecon -Rv on the affected path, toggle the relevant boolean with setsebool -P, or generate a custom policy module with audit2allow
- Never permanently set SELinux to Permissive or Disabled as a fix — it eliminates your entire MAC layer
- Use audit2why and sealert to get human-readable explanations and suggested remediation commands before touching anything
| Method | When to Use | Time to Apply | Risk / Trade-off |
|---|---|---|---|
| restorecon -Rv <path> | File/dir has wrong context after copy or move | < 30 seconds | Very Low — only relabels, does not change policy |
| semanage fcontext + restorecon | Custom app path that must persist across relabels | 2–5 minutes | Low — policy is persistent and survives filesystem relabels |
| setsebool -P <bool> on | App needs a pre-defined permission toggle (e.g., httpd_can_network_connect) | < 10 seconds | Low-Medium — widens policy for entire service class |
| audit2allow -M + semodule -i | Novel access needed that no boolean covers | 5–15 minutes | Medium — custom module may be overly permissive if audit log is noisy |
| semanage port -a | Service binding to a non-standard port | < 30 seconds | Low — adds a single port label |
| chcon -t <type> <path> | Quick one-off test only — NOT for production | < 5 seconds | High — label reverts on next restorecon or relabel |
| setenforce 0 (Permissive) | Diagnosis only — never as a permanent fix | Immediate | Very High — disables MAC enforcement system-wide |
Understanding SELinux Permission Denied Errors
SELinux enforces Mandatory Access Control (MAC) on top of standard Unix DAC permissions. When a process attempts an action that SELinux policy forbids, the kernel issues an Access Vector Cache (AVC) denial, blocks the syscall, and logs a structured audit record. Your application sees a generic Permission denied (errno 13) even though ls -l shows the file is world-readable.
The critical insight: a permission denied that disappears after setenforce 0 is always an SELinux denial, not a filesystem permissions issue.
Common application-level error messages that indicate an underlying AVC denial:
nginx: [crit] open() "/var/lib/myapp/data" failed (13: Permission denied)
Python: PermissionError: [Errno 13] Permission denied: '/srv/myapp/config.yaml'
systemd[1]: myapp.service: Failed to set up mount namespacing: Permission denied
PostgreSQL: FATAL: could not open file "/data/pgdata/PG_VERSION": Permission denied
Step 1: Locate the AVC Denial
All AVC denials are written to /var/log/audit/audit.log. The raw format is machine-readable but dense:
type=AVC msg=audit(1708723456.123:4521): avc: denied { read } for pid=12345 comm="nginx" name="myapp" dev="sda1" ino=12345678 scontext=system_u:system_r:httpd_t:s0 tcontext=unconfined_u:object_r:var_t:s0 tclass=dir permissive=0
Key fields to read:
- scontext: the SELinux label of the process (source context)
- tcontext: the SELinux label of the target file/socket/port
- tclass: the object class (file, dir, tcp_socket, process, etc.)
- { read write execute }: the specific permissions denied
Find recent denials:
# Last 20 AVC denials, newest first
ausearch -m avc -ts recent | head -80
# Denials for a specific process name
ausearch -m avc -c nginx
# Denials in a time range
ausearch -m avc -ts '02/23/2026 08:00:00' -te '02/23/2026 09:00:00'
Step 2: Translate the Denial to Human Language
audit2why converts raw AVC records into plain-English explanations and suggests remediation:
# Pipe recent denials through audit2why
ausearch -m avc -ts recent | audit2why
# Analyze the entire audit log
audit2why < /var/log/audit/audit.log
Sample audit2why output:
type=AVC msg=audit(1708723456.123:4521): avc: denied { read } for pid=12345 comm="nginx" ...
Was caused by:
Missing type enforcement (TE) allow rule.
You can use audit2allow to generate a loadable module to allow this access.
If you have setroubleshoot-server installed, sealert provides even richer output:
sealert -a /var/log/audit/audit.log
Step 3: Identify the Root Cause Category
SELinux denials almost always fall into one of four categories:
Category A — Wrong File Context
This is the most common cause. Files copied with cp or rsync from a differently-labeled source inherit the wrong context. Files created by an application outside its standard directories also get wrong labels.
Diagnose:
# Show current context vs. what policy expects
ls -Z /srv/myapp/
semanage fcontext -l | grep '/var/www'
If the current label (from ls -Z) differs from what semanage fcontext -l says it should be, run:
restorecon -Rv /srv/myapp/
The -R is recursive, -v prints what changed.
Category B — Boolean Not Enabled SELinux ships with tuneable booleans that enable common but non-default behaviors (e.g., allowing Apache to make outbound network connections, allowing services to write to home directories).
# List all booleans related to httpd
getsebool -a | grep httpd
# Get description of a specific boolean
semanage boolean -l | grep httpd_can_network_connect
# Enable a boolean persistently
setsebool -P httpd_can_network_connect on
audit2why will explicitly tell you when a boolean is the fix — look for output containing If you want to allow....
Category C — Non-Standard Port
If your service binds to a port that isn't labeled for its domain, SELinux blocks the bind() syscall.
# Check what label port 8080 has
semanage port -l | grep 8080
# Add label for your custom port
semanage port -a -t http_port_t -p tcp 8443
# Or modify an existing entry
semanage port -m -t http_port_t -p tcp 8443
Category D — Novel Access Requiring a Custom Policy
When restorecon, booleans, and port labels don't solve the problem, you need a custom policy module. Use audit2allow to generate one from the denial:
# Generate a named module from recent denials
ausearch -m avc -ts recent | audit2allow -M myapp_policy
# Inspect the generated .te file BEFORE loading it
cat myapp_policy.te
# Load the module
semodule -i myapp_policy.pp
# Verify it loaded
semodule -l | grep myapp_policy
Warning: Only run audit2allow against denials from a clean test run of your specific application. If the audit log contains unrelated denials from other services, your generated module will be overly permissive. Use -ts recent or filter with -c <comm> to isolate denials.
Step 4: Verify the Fix
After applying a fix, confirm SELinux no longer blocks the operation:
# Restart the service and check for new denials
systemctl restart myapp
ausearch -m avc -ts recent
# Confirm the service is functional
systemctl status myapp
curl -I http://localhost/health
If denials stop and the service works, document what you changed. If denials continue, repeat from Step 1 — there may be multiple distinct denials that appear sequentially.
Persistent Custom File Context Rules
If your application stores data in a non-standard path (e.g., /data/myapp instead of /var/lib/myapp), you must add a persistent fcontext rule so relabels don't undo your fix:
# Add the rule (survives relabels and OS upgrades)
semanage fcontext -a -t httpd_sys_content_t '/data/myapp(/.*)?'
# Apply it immediately
restorecon -Rv /data/myapp
# Verify
ls -Z /data/myapp
Without semanage fcontext, a touch /.autorelabel && reboot will revert all chcon and restorecon changes.
Diagnosis-Only: Permissive Mode for a Single Domain
If you need to test whether SELinux is causing an intermittent problem without disabling it globally:
# Set a single domain permissive (logs but doesn't block)
semanage permissive -a httpd_t
# Test your application...
# Remove permissive exception when done
semanage permissive -d httpd_t
This is far safer than setenforce 0 which drops enforcement for the entire system.
Frequently Asked Questions
#!/usr/bin/env bash
# SELinux AVC Denial Diagnostic Script
# Run as root on RHEL/CentOS/Fedora/Rocky/AlmaLinux
set -euo pipefail
APP_COMM="${1:-}"
APP_PATH="${2:-}"
echo "=== SELinux Status ==="
getenforce
sestatus | grep -E 'SELinux status|SELinuxfs mount|Current mode|Policy MLS'
echo ""
echo "=== Recent AVC Denials (last 10 minutes) ==="
if [[ -n "$APP_COMM" ]]; then
ausearch -m avc -c "$APP_COMM" -ts recent 2>/dev/null || echo "No denials found for comm=$APP_COMM"
else
ausearch -m avc -ts recent 2>/dev/null | tail -40 || echo "No recent AVC denials found"
fi
echo ""
echo "=== Human-Readable Explanation ==="
if [[ -n "$APP_COMM" ]]; then
ausearch -m avc -c "$APP_COMM" -ts recent 2>/dev/null | audit2why || true
else
ausearch -m avc -ts recent 2>/dev/null | audit2why || true
fi
if [[ -n "$APP_PATH" ]]; then
echo ""
echo "=== File Context for $APP_PATH ==="
ls -Z "$APP_PATH" 2>/dev/null || echo "Path not found"
echo ""
echo "=== Expected Context (from policy) ==="
semanage fcontext -l | grep "${APP_PATH%/*}" | head -20 || echo "No policy rule for this path prefix"
fi
echo ""
echo "=== Loaded Custom SELinux Modules ==="
semodule -l | grep -v "^base\|^kernel" | head -20
echo ""
echo "=== Common Booleans (check if relevant ones need enabling) ==="
getsebool -a | grep -E 'httpd|nginx|apache|mysql|postgres|ssh|ftp|samba|nfs|git|container' | sort
echo ""
echo "=== Suggested Fix Commands ==="
if [[ -n "$APP_COMM" ]]; then
echo "# Generate and inspect custom policy module:"
echo "ausearch -m avc -c $APP_COMM -ts recent | audit2allow -M ${APP_COMM}_fix"
echo "cat ${APP_COMM}_fix.te # REVIEW BEFORE LOADING"
echo "semodule -i ${APP_COMM}_fix.pp"
fi
if [[ -n "$APP_PATH" ]]; then
echo ""
echo "# Restore file contexts:"
echo "restorecon -Rv $APP_PATH"
echo ""
echo "# Add persistent fcontext rule (replace TYPE with correct type):"
echo "semanage fcontext -a -t TYPE '${APP_PATH}(/.*)?'"
echo "restorecon -Rv $APP_PATH"
fi
echo ""
echo "Done. Review output above before applying any fix."
Error Medic Editorial
The Error Medic Editorial team is composed of senior SREs and Linux systems engineers with collective experience spanning Red Hat Enterprise Linux, Kubernetes, and cloud-native infrastructure. Contributors hold RHCE, CKA, and LFCS certifications and have operated SELinux-enforcing environments in regulated industries including finance and healthcare.
Sources
- https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/9/html/using_selinux/troubleshooting-problems-related-to-selinux_using-selinux
- https://wiki.gentoo.org/wiki/SELinux/Tutorials/Where_to_find_SELinux_permission_denial_details
- https://danwalsh.livejournal.com/66630.html
- https://github.com/SELinuxProject/selinux/wiki/Troubleshooting
- https://stackoverflow.com/questions/6710356/selinux-permission-denied-for-web-server-on-centos