Skip to content
DevOps AI ToolKit
Newsletter
All guides
AI for DevOps Security & Hardening By James Joyner IV · · 10 min read

TLS & Security Error Guide: 'SELinux is preventing ...' (AVC Denial)

Fix SELinux AVC denials: read avc: denied messages with ausearch and sealert, correct file contexts, ports, and booleans, then build a targeted policy module.

  • #security
  • #troubleshooting
  • #errors
  • #selinux

Overview

An SELinux Access Vector Cache (AVC) denial means a process’s security context was not permitted to perform an operation on a target object’s context, so the kernel blocked it. The application sees a generic Permission denied (EACCES), but the real reason is recorded in the audit log as an avc: denied message. The fix is almost never chmod — it is correcting an SELinux context, boolean, or port label.

A raw denial in the audit log looks like:

type=AVC msg=audit(1750684931.412:1187): avc:  denied  { name_bind } for  pid=2244 comm="nginx" src=8443 scontext=system_u:system_r:httpd_t:s0 tcontext=system_u:object_r:unreserved_port_t:s0 tclass=tcp_socket permissive=0

setroubleshoot turns the same event into a human-readable summary:

SELinux is preventing /usr/sbin/nginx from name_bind access on the tcp_socket port 8443.

The two key fields are scontext (the process’s domain, e.g. httpd_t) and tcontext/tclass (what it tried to touch). permissive=0 means it was actively blocked; in permissive mode it would have been logged but allowed.

Symptoms

  • A service fails with Permission denied even though Unix permissions look correct.
  • The failure disappears when you run setenforce 0 (permissive) — a strong sign it is SELinux.
  • type=AVC ... denied lines appear in /var/log/audit/audit.log.
  • sealert / journal shows “SELinux is preventing …”.
sudo ausearch -m avc -ts recent 2>/dev/null | tail -3
type=AVC msg=audit(1750684931.412:1187): avc:  denied  { name_bind } for  pid=2244 comm="nginx" src=8443 scontext=system_u:system_r:httpd_t:s0 tcontext=system_u:object_r:unreserved_port_t:s0 tclass=tcp_socket permissive=0
getenforce
Enforcing

Common Root Causes

1. Wrong file/directory context

Files copied or moved (mv, cp -a from another tree, an extracted tarball) keep the source context instead of inheriting the destination’s expected label.

ls -Z /srv/web/index.html
sudo matchpathcon /srv/web/index.html
unconfined_u:object_r:user_home_t:s0 /srv/web/index.html
/srv/web/index.html	system_u:object_r:httpd_sys_content_t:s0

The file is user_home_t but the web server expects httpd_sys_content_t — a mismatch httpd cannot read.

2. Non-default port not labeled for the service

A daemon told to listen on a custom port is blocked because that port number lacks the service’s port type.

sudo semanage port -l | grep -w http_port_t
http_port_t                    tcp      80, 81, 443, 488, 8008, 8009, 8443, 9000

If your custom port (e.g. 8089) is not listed, name_bind is denied.

3. A required boolean is off

Many integrations (httpd making network connections, NFS home dirs, etc.) are gated behind SELinux booleans that default to off.

getsebool -a | grep -E 'httpd_can_network_connect'
httpd_can_network_connect --> off

With this off, a reverse proxy or app server’s outbound connection is denied.

4. Process running in an unexpected domain

The binary is mislabeled, so it runs in the wrong domain and lacks the rules its real domain would have.

ps -eZ | grep nginx
ls -Z /usr/local/bin/customapp
system_u:system_r:unconfined_service_t:s0 2244 ? nginx
unconfined_u:object_r:user_tmp_t:s0 /usr/local/bin/customapp

A binary in user_tmp_t will not transition into its proper service domain.

5. Volume/bind mount missing a context label

Container or NFS mounts without an SELinux context expose files the container domain cannot access.

mount | grep /data
ls -Zd /data
/dev/sdb1 on /data type xfs (rw,relatime)
system_u:object_r:default_t:s0 /data

default_t is unreadable by container_t; the mount needs a context= option or relabel with :Z.

6. Dynamic content directory not writable by the service domain

The app needs to write (cache, uploads) but the directory carries a read-only content label.

sudo ausearch -m avc -ts recent 2>/dev/null | grep -E 'write|httpd_sys_rw_content_t' | tail -1
ls -Z /srv/web/uploads
type=AVC ... denied  { write } ... scontext=...:httpd_t:s0 tcontext=...:httpd_sys_content_t:s0 tclass=dir

httpd_sys_content_t is read-only for httpd; writable content needs httpd_sys_rw_content_t.

Diagnostic Workflow

Step 1: Confirm SELinux is the cause

getenforce
sudo ausearch -m avc -ts recent 2>/dev/null | tail -5

If denials line up with the failure timestamps, SELinux is responsible.

Step 2: Get a human-readable explanation and suggested fix

sudo sealert -a /var/log/audit/audit.log 2>/dev/null | grep -A8 'SELinux is preventing'

setroubleshoot summarizes the scontext/tcontext and often names the exact boolean or semanage command.

Step 3: Identify whether it is a context, port, or boolean issue

# context mismatch?
ls -Z <PATH>; sudo matchpathcon <PATH>
# port label?
sudo semanage port -l | grep -w <service>_port_t
# boolean?
getsebool -a | grep <service>

Step 4: Apply the minimal correct fix (not setenforce 0)

# fix a file context permanently
sudo semanage fcontext -a -t httpd_sys_content_t "/srv/web(/.*)?"
sudo restorecon -Rv /srv/web
# add a custom port
sudo semanage port -a -t http_port_t -p tcp 8089
# flip a boolean persistently
sudo setsebool -P httpd_can_network_connect on

Step 5: Re-test, and only then consider a custom module for leftover denials

# if a legitimate denial has no boolean/context fix, build a scoped module:
sudo ausearch -m avc -ts recent 2>/dev/null | audit2allow -M customapp_local
sudo semodule -i customapp_local.pp
# verify no new denials
sudo ausearch -m avc -ts recent 2>/dev/null | tail

Example Root Cause Analysis

After moving a site from /var/www/html to /srv/web, nginx returns 403 for every page, but ls -l shows world-readable files owned by nginx. Permissive mode confirms SELinux:

sudo setenforce 0
curl -s -o /dev/null -w '%{http_code}\n' http://localhost/
sudo setenforce 1
200

It works in permissive, so a denial is the cause. The audit log shows a read denial against the content:

sudo ausearch -m avc -ts recent 2>/dev/null | grep read | tail -1
type=AVC ... denied  { read } for ... comm="nginx" name="index.html" scontext=...:httpd_t:s0 tcontext=...:default_t:s0 tclass=file

/srv is labeled default_t, not httpd_sys_content_t, because it was never a web root. The files were moved with mv, preserving their old labels rather than inheriting a web context.

Fix: define the correct fcontext for /srv/web and relabel:

sudo semanage fcontext -a -t httpd_sys_content_t "/srv/web(/.*)?"
sudo restorecon -Rv /srv/web
ls -Z /srv/web/index.html
Relabeled /srv/web/index.html from unconfined_u:object_r:default_t:s0 to system_u:object_r:httpd_sys_content_t:s0

nginx serves the site in enforcing mode with no further denials.

Prevention Best Practices

  • Never leave setenforce 0 as the fix — it disables protection host-wide. Use it only to confirm SELinux, then revert.
  • Use semanage fcontext + restorecon for permanent labels so relabels and restores keep the right context; one-off chcon is lost on a relabel.
  • Register custom ports with semanage port and flip integrations with setsebool -P rather than writing modules for cases SELinux already supports.
  • When you must allow a genuinely new access, generate a scoped module with audit2allow -M, review the rules, and avoid blanket allows.
  • Keep setroubleshoot-server installed on servers so denials produce actionable sealert summaries; see the security hardening guides for a context-labeling baseline.
  • For triaging a burst of denials after a deploy, the free incident assistant can group avc: denied lines by scontext/tclass and suggest the boolean or fcontext to set.

Quick Command Reference

# Is SELinux enforcing, and are there recent denials?
getenforce
sudo ausearch -m avc -ts recent 2>/dev/null | tail

# Human-readable explanation + suggested fix
sudo sealert -a /var/log/audit/audit.log 2>/dev/null | grep -A8 'SELinux is preventing'

# Compare actual vs expected context
ls -Z <PATH>; sudo matchpathcon <PATH>

# Port labels and booleans
sudo semanage port -l | grep <service>_port_t
getsebool -a | grep <service>

# Permanent fixes
sudo semanage fcontext -a -t httpd_sys_content_t "/srv/web(/.*)?" && sudo restorecon -Rv /srv/web
sudo semanage port -a -t http_port_t -p tcp 8089
sudo setsebool -P httpd_can_network_connect on

# Scoped policy module (last resort)
sudo ausearch -m avc -ts recent 2>/dev/null | audit2allow -M mymod && sudo semodule -i mymod.pp

Conclusion

An SELinux AVC denial is a context/policy mismatch that surfaces as a generic Permission denied. Work it from the audit log, not from chmod:

  1. A file or directory carries the wrong context — fix with semanage fcontext + restorecon.
  2. A custom port lacks the service’s port type — add it with semanage port.
  3. A required boolean is off — flip it persistently with setsebool -P.
  4. The process runs in the wrong domain because its binary is mislabeled.
  5. A bind/volume mount lacks an SELinux context (context= / :Z).
  6. A writable directory carries a read-only content label.

Confirm with permissive mode, read the scontext/tcontext in the denial, and apply the narrowest correct fix — reserve custom audit2allow modules for accesses SELinux has no built-in toggle for.

Free download · 368-page PDF

Download the Free 500-Prompt DevOps AI Toolkit

500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.

  • 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
  • Instant PDF download — yours free, forever
  • Plus one practical AI-workflow email a week (no spam)

Single opt-in · unsubscribe anytime · no spam.