Fixing SELinux Errors Without Disabling It
How to troubleshoot and resolve SELinux denials on Linux without disabling SELinux, using audit logs, setsebool, and proper file contexts.
Fixing SELinux Errors Without Disabling It
Disabling SELinux is the lazy fix. It removes the symptom but leaves your system vulnerable. When a web application on a CentOS 7 server started returning 503 errors, SELinux was the culprit. Instead of disabling it, I learned to work with it. This guide shows how.
Environment
- OS: CentOS 7.9 (with SELinux Enforcing)
- Application: nginx + PHP-FPM + MySQL
- SELinux mode: Enforcing
$ getenforce
Enforcing
$ sestatus
SELinux status: enabled
SELinuxfs mount: /sys/fs/selinux
SELinux root directory: /etc/selinux
Loaded policy name: targeted
Current mode: enforcing
Mode from config file: enforcingThe Problem
After migrating a PHP application to a new directory, nginx started returning 503 errors:
$ curl -I http://localhost/app/
HTTP/1.1 503 Service Temporarily Unavailable
$ sudo tail -f /var/log/nginx/error.log
2025/06/22 10:00:00 [error] 1234#1234: *1 connect() failed (111: Connection refused)
2025/06/22 10:00:00 [error] 1234#1234: *1 upstream prematurely closed connection
# PHP-FPM was running but nginx couldn't connect
$ sudo systemctl status php-fpm
● php-fpm.service - The PHP FastCGI Process Manager
Active: active (running) since Sat 2025-06-22 09:55:00 WET; 5min ago
# Check SELinux denials
$ sudo ausearch -m AVC --today | tail -20
type=AVC msg=audit(1719030000.123:456): avc: denied { name_connect } for
pid=1234 comm="nginx" dest=9000
scontext=system_u:system_r:httpd_t:s0
tcontext=s0 tclass=tcp_socket
type=AVC msg=audit(1719030001.234:457): avc: denied { read write } for
pid=5678 comm="php-fpm" name="app.log"
scontext=system_u:system_r:httpd_t:s0
tcontext=unconfined_u:object_r:user_home_t:s0
tclass=fileSELinux is blocking nginx from connecting to PHP-FPM and PHP-FPM from writing to the new log file location.
Analysis
Step 1: Understand the Denials
# First denial: nginx cannot connect to port 9000
# httpd_t (nginx) cannot connect to unreserved port
# Second denial: PHP-FPM cannot write to new log location
# httpd_t cannot write to user_home_t contextStep 2: Check File Contexts
# Check current context of the new app directory
$ ls -Z /var/www/html/app/
drwxr-xr-x root root unconfined_u:object_r:user_home_t:s0 logs
-rw-r--r-- root root unconfined_u:object_r:httpd_sys_content_t:s0 index.php
# The logs directory has wrong context (user_home_t instead of httpd_sys_rw_content_t)Step 3: Check Port Configuration
# Check if port 9000 is allowed for httpd
$ sudo semanage port -l | grep http
http_cache_port_t tcp 8080, 8118, 8123, 10001-10010
http_port_t tcp 80, 443, 488, 8008, 8009, 8443, 9000
# Port 9000 is NOT in the http_port_t listSolution
Fix 1: Restore File Contexts
# Set correct context for the new directory
$ sudo semanage fcontext -a -t httpd_sys_rw_content_t "/var/www/html/app/logs(/.*)?"
$ sudo restorecon -Rv /var/www/html/app/logs
# Verify
$ ls -Z /var/www/html/app/logs/
drwxr-xr-x root root system_u:object_r:httpd_sys_rw_content_t:s0 logsFix 2: Allow Port 9000 for HTTP
# Add port 9000 to httpd port type
$ sudo semanage port -a -t http_port_t -p tcp 9000
# Verify
$ sudo semanage port -l | grep 9000
http_port_t tcp 9000Fix 3: Use setsebool for Common Booleans
# Allow httpd to connect to network (for PHP-FPM)
$ sudo setsebool -P httpd_can_network_connect 1
# Allow httpd to send mail
$ sudo setsebool -P httpd_can_sendmail 1
# Allow httpd to use NFS/CIFS mounts
$ sudo setsebool -P httpd_use_nfs 1
# List all httpd booleans
$ getsebool -a | grep httpd
httpd_can_network_connect --> off
httpd_can_network_connect_db --> off
httpd_can_sendmail --> offFix 4: Create Custom Policy Module
When booleans and contexts are not enough:
# Generate a custom policy from denials
$ sudo ausearch -m AVC --today | audit2allow -M my_custom_policy
# Review the generated policy
$ cat my_custom_policy.te
module my_custom_policy 1.0;
require {
type httpd_t;
type user_home_t;
class file { read write open };
class tcp_socket { name_connect };
}
#============= httpd_t ==============
allow httpd_t user_home_t:file { read write open };
allow httpd_t unreserved_port_t:tcp_socket name_connect;
# Install the policy
$ sudo semodule -i my_custom_policy.pp
# Verify
$ sudo semodule -l | grep my_custom_policy
my_custom_policy 1.0Fix 5: Troubleshooting Workflow
# Step 1: Find all denials
$ sudo ausearch -m AVC --start today --input-logs
# Step 2: Generate policy suggestions
$ sudo ausearch -m AVC --start today | audit2allow -a
# Step 3: Check if a boolean already exists
$ sudo semanage boolean -l | grep keyword
# Step 4: If no boolean, create custom module
$ sudo ausearch -m AVC --start today | audit2allow -M custom
$ sudo semodule -i custom.pp
# Step 5: Test
$ sudo ausearch -m AVC --start recent
# Should show no new denials for the same issueCommon SELinux Denials and Fixes
| Denial | Cause | Fix |
|---|---|---|
| `name_connect` | Port not labeled | `semanage port -a -t http_port_t -p tcp PORT` |
| `read write` on file | Wrong file context | `semanage fcontext` + `restorecon` |
| `execmem` | Application needs memory execution | `setsebool -P allow_execmem 1` |
| `name_bind` | Non-standard port for service | `semanage port -a -t http_port_t -p tcp PORT` |
| `dbus` | IPC communication blocked | `setsebool -P allow_user_execs 1` |
Verify SELinux Status
# Check if SELinux is still enforcing
$ getenforce
Enforcing
# Check for recent denials
$ sudo ausearch -m AVC --recent
# No output means no recent denials
# Check audit log
$ sudo ausearch -m AVC --start recent -i | head -10
# Should be cleanLessons Learned
- Never disable SELinux - it protects against zero-day exploits and privilege escalation.
- Learn
audit2allow- it is the fastest way to create targeted policies. - Use
setsebool -Pfor boolean changes to make them persistent across reboots. - Always check
ausearchbefore assuming SELinux is the problem. - Document custom policies - future you will thank present you.
This blog does not accept any external sponsorships, affiliate marketing, or ad revenue.