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 deniedeven though Unix permissions look correct. - The failure disappears when you run
setenforce 0(permissive) — a strong sign it is SELinux. type=AVC ... deniedlines 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 0as the fix — it disables protection host-wide. Use it only to confirm SELinux, then revert. - Use
semanage fcontext+restoreconfor permanent labels so relabels and restores keep the right context; one-offchconis lost on a relabel. - Register custom ports with
semanage portand flip integrations withsetsebool -Prather 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-serverinstalled on servers so denials produce actionablesealertsummaries; 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: deniedlines 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:
- A file or directory carries the wrong context — fix with
semanage fcontext+restorecon. - A custom port lacks the service’s port type — add it with
semanage port. - A required boolean is off — flip it persistently with
setsebool -P. - The process runs in the wrong domain because its binary is mislabeled.
- A bind/volume mount lacks an SELinux context (
context=/:Z). - 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.
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.