CentOS SELinux: Diagnosing Denials and Deciding When to Write Custom Policy
CentOS ships with SELinux enforcing by default. Learn how to read AVC denials in audit logs, test booleans before writing custom modules, and keep enforcing mode enabled.
04 Mar 2026, 05:03 UTC

You point Apache at a new document root, restart the service, and get a 403 Forbidden. File permissions look correct, the firewall is open, and the configuration syntax checks out. On CentOS, the next suspect is often SELinux, specifically the targeted policy that confines httpd by default. The useful takeaway: capture the denial from the audit log, test an existing boolean, and reserve custom policy modules for cases where no supported switch exists.
Treating enforcing mode as the supported default—and working through denials systematically—is usually faster than disabling SELinux and hoping the problem stays fixed.
Why CentOS Enforces SELinux by Default
CentOS ships with SELinux in enforcing mode using the targeted policy. In this policy, high-risk network daemons such as httpd, sshd, and named run in confined domains, while most ordinary user processes remain unconfined. The goal is to limit the blast radius of a compromised service without interrupting normal desktop or shell use.
You can confirm the current state with getenforce and sestatus, both runnable as a normal user. To verify the boot-time setting, inspect /etc/selinux/config as root. Different CentOS releases ship different policy versions and boolean sets, so verify names on the exact target release rather than assuming they carry over.
Capture the Denial Before Changing Anything
SELinux records denials as AVC (Access Vector Cache) messages. On a full server install, the audit daemon writes these to /var/log/audit/audit.log. On minimal or container images, auditd may not be running, which means denials are not logged at all. As root, check the service first:
systemctl status auditd
If it is inactive, enable and start it before reproducing the failure. Then, in a second terminal, run:
ausearch -m avc -ts recent
This command reads the audit log and shows recent denials. Look for the source context (for example, httpd_t), the target context, and the denied permission. If setroubleshoot-server is installed, sealert -a /var/log/audit/audit.log produces a more readable explanation of the same events.
Test an Existing Boolean First
Before writing policy, check whether the SELinux policy already provides a boolean—a supported, narrowly scoped switch—for your use case. List the current booleans and filter for the relevant service:
getsebool -a | grep httpd
Common candidates include httpd_can_network_connect and httpd_read_user_content. If a boolean looks relevant, enable it persistently with the -P flag so it survives reboots:
setsebool -P httpd_read_user_content on
Then retry the failing operation. If the denial disappears, you are done. Because -P writes to the policy store, this is a persistent state change; to roll back, run setsebool -P httpd_read_user_content off. Boolean names and availability vary by CentOS release, so confirm the exact name on your system.
When a Custom Module Is Justified
If no boolean fits, the next step is a custom policy module. The audit2allow tool can scaffold a module from denial logs, but its output reflects only what was denied during a single reproduction—and it can be far more permissive than the application needs. Review every generated rule manually before installing it.
File context labels are another common source of confusion. Moving a directory with mv preserves its original label, while cp typically creates files with a default label that may be incorrect. If the target path has the wrong context, adjust it with semanage fcontext and apply the change with restorecon:
semanage fcontext -a "/srv/custom_app(/.*)?" -t httpd_sys_content_t
restorecon -Rv /srv/custom_app
If a custom module is still required after labels and booleans are correct, install it and verify it loaded:
semodule -i /root/custom_httpd.pp
semodule -l | grep custom_httpd
This changes system state; to remove the module later, run semodule -r custom_httpd.
Worked Example: Serving Content from /srv/custom_app
Suppose you deploy an application under /srv/custom_app on a CentOS 8 or 9 server and Apache returns 403 Forbidden. You confirm auditd is active, reproduce the request, and see an AVC denial showing httpd_t denied read access to the new path.
- Run
ls -Z /srv/custom_appto inspect the current file context. If the label is nothttpd_sys_content_t, the path is mislabeled. - Apply the correct context with
semanage fcontextandrestoreconas shown above. - If the denial persists, check
getsebool -a | grep httpd_read_user_content. If it is off, enable it withsetsebool -Pand retry. - If the denial still appears in
ausearch -m avc -ts recent, generate a module withaudit2allow, inspect the rules, and only then install it withsemodule -i.
Throughout this process, keep SELinux in enforcing mode. Running setenforce 0 or setting SELINUX=disabled in /etc/selinux/config is not a supported workaround for application bugs; it masks misconfigurations that often reappear after migration.
Trade-offs and Limitations
| Approach | Strength | Limitation |
|---|---|---|
| Existing boolean | Supported, narrow, persistent with -P | May not exist for your exact use case; names vary by release |
Custom module via audit2allow | Addresses denials with no boolean | Can grant overly broad access if not manually reviewed |
| Disabling SELinux | Immediate silence | Unsupported; hides real misconfigurations and weakens the system |
Two additional limitations matter in practice. First, on minimal images the audit daemon may be absent, so denials are invisible until auditd is installed and enabled. Second, the unconfined_service and unconfined_user domains exist, but placing a network daemon in an unconfined domain is generally discouraged because it removes the confinement that targeted policy provides.
Actionable Checklist
- Confirm mode and policy with
getenforceandsestatus. - Ensure
auditdis running before reproducing the failure. - Capture the exact denial with
ausearch -m avc -ts recent. - Search for a relevant boolean and test it with
setsebool -P. - Correct file contexts with
semanage fcontextandrestoreconbefore writing policy. - Only if no boolean fits, scaffold a module with
audit2allow, review it, install withsemodule -i, and verify withsemodule -l.
Keeping SELinux in enforcing mode is not an obstacle to work around; it is a supported security boundary. Diagnosing denials properly takes a few extra minutes, but it leaves the system in a state you can audit, migrate, and trust.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.