Fixing SELinux Denials Blocking a Legitimate Service
A service failing only with SELinux enforcing needs the actual denial read from audit.log and fixed with a targeted policy module, not disabled protection.
A service failing only under SELinux enforcing mode, with no obvious application-level error, is almost always a policy denial — the fix is identifying exactly what was denied and granting precisely that, not disabling SELinux.
Step 1: confirm SELinux is actually the cause
setenforce 0
# retest the failing operation
setenforce 1
If the problem disappears in permissive mode and returns in enforcing mode, SELinux is confirmed as the cause — this is a diagnostic step, not a fix; leaving SELinux permissive/disabled removes real protection rather than solving the underlying policy gap.
Step 2: find the specific denial in the audit log
ausearch -m avc -ts recent
This shows the exact denial: which process, which file or port, and which specific permission (read, write, name_connect) was refused.
Step 3: get a human-readable explanation
sealert -a /var/log/audit/audit.log
sealert (from setroubleshoot) translates raw AVC denials into a plain-language description and, frequently, a suggested fix.
Step 4: check if the fix is simply a mislabeled file
ls -Z /path/to/file
restorecon -v /path/to/file
A very common cause: a file moved or created outside its expected context has the wrong SELinux label — restorecon resets it to the policy-defined default for its location, which resolves a large fraction of denials without any policy changes at all.
Step 5: check if a boolean already exists for this exact scenario
getsebool -a | grep -i httpd
setsebool -P httpd_can_network_connect on
SELinux ships many pre-defined booleans for common scenarios (a web server making outbound connections, for instance) — checking for an existing boolean before writing custom policy is almost always the simpler, better-supported fix.
Step 6: generate a custom policy module for a genuinely novel denial
ausearch -m avc -ts recent | audit2allow -M mymodule
semodule -i mymodule.pp
audit2allow builds a minimal policy module granting exactly the permissions that were actually denied and logged — narrowly scoped to the specific denial, not a blanket exception.
Step 7: verify the fix resolved it without introducing new denials
ausearch -m avc -ts recent
Retest the original operation and confirm no new denials appear — a policy module that’s too broad can mask a further, more serious problem it happens to also permit.
Why disabling SELinux is never the right fix
Setting SELinux to permissive or disabled removes mandatory access control system-wide, not just for the one denial you’re troubleshooting — exactly the protection layer covered in the SELinux/AppArmor deep-dive that limits what a compromised process can do even with correct discretionary permissions. restorecon, existing booleans, and targeted audit2allow modules address the actual reported denial with much smaller, well-understood scope, which is why they’re the right tools reached for first.
Step 8: make a label fix survive the next full relabel
semanage fcontext -a -t httpd_sys_content_t "/srv/webfiles(/.*)?"
restorecon -Rv /srv/webfiles
Running restorecon alone fixes the label on disk right now, but it resets each path back to whatever the policy’s file-context rules already say that path should be — if the actual problem is that no rule exists for a nonstandard location (content served from /srv/webfiles instead of the default /var/www/html, for instance), restorecon has nothing correct to restore to, and the fix keeps reverting. semanage fcontext -a adds a new persistent rule to the policy itself, and only after that rule exists does restorecon have a genuinely correct target to reset the path to — the two commands solve different halves of the same problem, and skipping the semanage step is why a restorecon fix can appear to spontaneously break again after a full system relabel.
Step 9: know the difference between a real denial and a merely logged one
ausearch -m avc -ts recent | audit2why
Not every AVC entry in the audit log represents something SELinux actually blocked — audit2why’s output distinguishes a genuine denial from a dontaudit rule that was deliberately suppressed from enforcement logging but still appears when auditing is forced on for debugging, and from a rule already covered by an existing boolean that simply wasn’t enabled. Treating every audit log line as an equally urgent denial to fix individually, rather than reading audit2why’s classification of each one first, wastes time chasing entries that were never actually blocking anything in the first place.
Related:
- SELinux vs. AppArmor: Mandatory Access Control on Linux
- Diagnosing a Linux Service That Fails Silently from a Permission Denial
Sources: