Error Medic

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.

Last updated:
Last verified:
1,987 words
Key Takeaways
  • 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
SELinux Fix Approaches Compared
MethodWhen to UseTime to ApplyRisk / Trade-off
restorecon -Rv <path>File/dir has wrong context after copy or move< 30 secondsVery Low — only relabels, does not change policy
semanage fcontext + restoreconCustom app path that must persist across relabels2–5 minutesLow — policy is persistent and survives filesystem relabels
setsebool -P <bool> onApp needs a pre-defined permission toggle (e.g., httpd_can_network_connect)< 10 secondsLow-Medium — widens policy for entire service class
audit2allow -M + semodule -iNovel access needed that no boolean covers5–15 minutesMedium — custom module may be overly permissive if audit log is noisy
semanage port -aService binding to a non-standard port< 30 secondsLow — adds a single port label
chcon -t <type> <path>Quick one-off test only — NOT for production< 5 secondsHigh — label reverts on next restorecon or relabel
setenforce 0 (Permissive)Diagnosis only — never as a permanent fixImmediateVery 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

bash
#!/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."
E

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

Related Articles in Selinux

Explore More Linux Sysadmin Guides