yum upgrades for production use, this is the repository for you.
Active subscription is required.
📅 Updated: February 18, 2026 (Originally published: August 7, 2017)
Every NGINX SELinux issue falls into one of three categories: a boolean that needs toggling, a file context that needs labeling, or a port that needs allowing. This guide covers all three with verified solutions.
SELinux is a mandatory access control (MAC) system built into the Linux kernel. It comes enabled by default on RHEL, CentOS, Rocky Linux, AlmaLinux, and Fedora. Many guides tell you to disable SELinux when something breaks. Never do this. Disabling SELinux removes a critical security layer and violates PCI-DSS, HIPAA, and other compliance requirements.
NGINX runs under the httpd_t SELinux domain — the same type used by Apache. This means all httpd_* booleans and file contexts apply to NGINX too. When NGINX SELinux denials occur, the fix is almost always a one-line command.
The GetPageSpeed NGINX module packages ship with -selinux subpackages that install automatically. If you use our repository, most NGINX SELinux issues are handled for you.
Essential SELinux Tools
Install the troubleshooting toolkit:
dnf install policycoreutils-python-utils setroubleshoot-server selinux-policy-doc
On RHEL 7/CentOS 7, the package name is policycoreutils-python instead.
Key commands you will use:
| Command | Purpose |
|---|---|
getenforce |
Check current SELinux mode (Enforcing/Permissive/Disabled) |
sestatus |
Detailed SELinux status |
getsebool -a \| grep httpd |
List all NGINX-related booleans |
setsebool -P boolean on |
Enable a boolean persistently |
semanage fcontext |
Manage permanent file context rules |
restorecon -Rv path |
Apply file context rules to files |
semanage port |
Manage port labels |
audit2why |
Explain why SELinux denied an action |
audit2allow |
Generate policy from denials |
sealert -a /var/log/audit/audit.log |
Comprehensive violation report |
For extensive documentation on all HTTP-related SELinux policy, run:
man httpd_selinux
NGINX SELinux Troubleshooting Workflow
When NGINX returns unexpected errors (502, 403, “Permission denied” in logs), follow this systematic approach:
Step 1. Confirm SELinux is the cause. Set NGINX to permissive mode (everything else stays enforcing):
semanage permissive -a httpd_t
Reload NGINX and test. If the problem disappears, SELinux caused it.
Step 2. Read the audit log to find the specific denial:
grep nginx /var/log/audit/audit.log | audit2why
The audit2why command explains each denial and often suggests the exact boolean to enable.
Step 3. Determine the fix category:
- Boolean — enable a pre-defined permission toggle (most common)
- File context — label files so NGINX can access them
- Port label — allow NGINX to bind to a non-standard port
- Custom policy — for rare cases where no boolean or context rule exists
Step 4. Apply the minimal fix using the appropriate command from the sections below.
Step 5. Return to enforcing mode:
semanage permissive -d httpd_t
NGINX SELinux Booleans Reference
SELinux booleans are on/off switches that control broad permission categories. Most NGINX SELinux issues are solved by enabling a single boolean.
List all booleans relevant to NGINX:
getsebool -a | grep httpd
Network Connection Booleans
These are the most commonly needed booleans. When NGINX acts as a reverse proxy, it must connect to backend services over the network. SELinux blocks this by default.
| Boolean | Default | When You Need It |
|---|---|---|
httpd_can_network_connect |
off | Proxy to any backend on any TCP port (Node.js, Django, Gunicorn, etc.) |
httpd_can_network_relay |
off | Proxy to HTTP/HTTPS ports only |
httpd_can_network_connect_db |
off | Connect to MySQL, PostgreSQL, or other database ports |
httpd_can_network_memcache |
off | Connect to Memcached |
httpd_can_network_redis |
off | Connect to Redis |
httpd_can_sendmail |
off | PHP or scripts sending email via sendmail/postfix |
httpd_can_connect_ftp |
off | FTP client connections |
httpd_can_connect_ldap |
off | LDAP authentication |
Critical distinction: httpd_can_network_connect allows connections to all TCP ports. httpd_can_network_relay only allows connections to ports already labeled as HTTP-related (http_port_t). If your backend runs on a standard HTTP port (80, 443, 8008, 8443, 9000), httpd_can_network_relay is the more restrictive (safer) choice. If it runs on any other port (3000, 5000, 8080, etc.), you need httpd_can_network_connect.
Enable a boolean persistently with the -P flag:
setsebool -P httpd_can_network_connect on
File and Module Booleans
| Boolean | Default | When You Need It |
|---|---|---|
httpd_setrlimit |
off | Using worker_rlimit_nofile directive |
httpd_execmem |
off | Modules requiring executable memory (ModSecurity, PageSpeed) |
httpd_use_nfs |
off | Serving files from NFS mounts |
httpd_use_cifs |
off | Serving files from CIFS/Samba mounts |
httpd_use_fusefs |
off | Serving files from FUSE filesystems |
httpd_enable_homedirs |
off | Serving user home directories (~/public_html) |
httpd_read_user_content |
off | Reading general user content |
httpd_unified |
off | Treat all httpd content types as read-write (overly permissive — avoid) |
domain_kernel_load_modules |
varies | kTLS (kernel TLS offload) — on by default in RHEL 9+/Rocky 10 |
NGINX SELinux File Contexts Reference
SELinux labels files with types that control what processes can access them. NGINX (httpd_t) can only read files labeled with httpd_* types.
| Type | Purpose | Default Paths |
|---|---|---|
httpd_sys_content_t |
Read-only web content | /var/www/html/, /srv/*/www/, /usr/share/nginx/ |
httpd_sys_rw_content_t |
Writable web content (uploads, cache) | WordPress wp-content/, upload directories |
httpd_config_t |
Configuration files | /etc/nginx/ |
httpd_log_t |
Log files | /var/log/nginx/ |
httpd_var_lib_t |
Variable library data, caches | /var/lib/nginx/ |
httpd_cache_t |
Cache directories | /var/cache/nginx/ |
httpd_var_run_t |
PID files and UNIX sockets | /run/nginx*, /run/php-fpm/ |
Permanent vs. Temporary Context Changes
Always use semanage fcontext for permanent rules. Never rely on chcon in production — it is temporary and gets overwritten by restorecon or filesystem relabeling:
# Permanent (survives restorecon and relabeling)
semanage fcontext -a -t httpd_sys_content_t "/opt/mysite(/.*)?"
restorecon -Rv /opt/mysite
# Temporary (DO NOT use in production)
chcon -t httpd_sys_content_t /opt/mysite/index.html
Verify a file’s context matches the policy:
matchpathcon -V /opt/mysite/index.html
Expected output: /opt/mysite/index.html verified.
Port Binding for Non-Standard Ports
SELinux restricts which ports NGINX can bind to. The default allowed ports are:
http_port_t tcp 80, 81, 443, 488, 8008, 8009, 8443, 9000
If NGINX needs to listen on a port not in this list, you will see in the error log:
bind() to 0.0.0.0:4000 failed (13: Permission denied)
And in the audit log:
avc: denied { name_bind } for comm="nginx" src=4000
scontext=system_u:system_r:httpd_t:s0 tcontext=system_u:object_r:unreserved_port_t:s0
Add the port to http_port_t:
semanage port -a -t http_port_t -p tcp 4000
List currently allowed ports:
semanage port -l | grep http_port_t
Real-World NGINX SELinux Scenarios
1. Reverse Proxy Returns 502 Bad Gateway
This is the single most common NGINX SELinux problem. You configure proxy_pass to a backend and get a 502 Bad Gateway error.
Error log:
connect() to 127.0.0.1:3000 failed (13: Permission denied) while connecting to upstream
Audit log:
avc: denied { name_connect } for comm="nginx" dest=3000
scontext=system_u:system_r:httpd_t:s0 tcontext=system_u:object_r:ntop_port_t:s0
Fix:
setsebool -P httpd_can_network_connect on
If the backend runs on a standard HTTP port (80, 443, 8008, 8443, 9000), you can use the more restrictive alternative:
setsebool -P httpd_can_network_relay on
2. Static Files Return 403 Forbidden
Files have correct Unix permissions (chmod/chown) but NGINX returns 403. This happens when files are in a non-standard location or were copied from another context.
Error log:
stat() "/opt/mysite/index.html" failed (13: Permission denied)
Diagnosis:
ls -Z /opt/mysite/
If the type is NOT httpd_sys_content_t (for example, default_t, user_home_t, or var_t), SELinux is the cause.
Fix:
semanage fcontext -a -t httpd_sys_content_t "/opt/mysite(/.*)?"
restorecon -Rv /opt/mysite
3. Cannot Bind to Non-Standard Port
NGINX fails to start or reload when listening on ports outside the default http_port_t list.
Error log:
bind() to 0.0.0.0:8080 failed (13: Permission denied)
Fix:
semanage port -a -t http_port_t -p tcp 8080
4. PHP-FPM Socket Permission Denied
NGINX cannot connect to the PHP-FPM UNIX socket.
Error log:
connect() to unix:/run/php-fpm/www.sock failed (13: Permission denied)
The default SELinux policy already labels /run/php-fpm/ as httpd_var_run_t, which NGINX can access. If your socket is in a non-standard location, use the /run/ path (on modern systems /var/run is a symlink to /run, and semanage fcontext requires the canonical path):
semanage fcontext -a -t httpd_var_run_t "/run/custom-php(/.*)?"
restorecon -Rv /run/custom-php
5. WordPress Cannot Write to Uploads
PHP-FPM (also running as httpd_t) needs write access to wp-content/uploads/, wp-content/cache/, and similar directories.
The default SELinux policy already includes:
/var/www/html(/.*)?/wp-content(/.*)? httpd_sys_rw_content_t
For non-standard directory structures (outside /var/www/html/):
semanage fcontext -a -t httpd_sys_rw_content_t "/srv/www/(.*)/httpdocs/wp-content(/.*)?"
restorecon -Rv /srv/www/*/httpdocs/wp-content
This is more secure than setting the httpd_unified boolean, which makes all httpd content writable.
6. FastCGI Cache in Wrong Directory
Storing the FastCGI cache inside /etc/nginx/ causes SELinux violations because /etc/nginx/ is labeled httpd_config_t (read-only configuration).
Bad configuration:
fastcgi_cache_path /etc/nginx/cache levels=1:2 keys_zone=microcache:10m max_size=256m inactive=30m;
Move the cache to /var/lib/nginx/ which is labeled httpd_var_lib_t:
mkdir -p /var/lib/nginx/microcache
chown -R nginx /var/lib/nginx
restorecon -Rv /var/lib/nginx
Correct configuration:
fastcgi_cache_path /var/lib/nginx/microcache levels=1:2 keys_zone=microcache:10m max_size=256m inactive=30m;
The same applies to proxy_cache_path — use /var/lib/nginx/ or /var/cache/nginx/ instead of /etc/nginx/.
7. worker_rlimit_nofile Silently Ignored
The worker_rlimit_nofile directive raises the file descriptor limit for NGINX worker processes. Without the httpd_setrlimit boolean, SELinux silently blocks this directive — NGINX starts without any error, but workers keep the default limit (typically 1024) instead of the configured value. This dontaudit behavior makes it particularly hard to diagnose. Enable the boolean:
setsebool -P httpd_setrlimit on
Verify the limit took effect by checking the worker process:
cat /proc/$(pgrep -f "worker process" | head -1)/limits | grep "open files"
8. kTLS Module Loading Denied
Modern NGINX versions can use kernel TLS acceleration (kTLS) for improved SSL/TLS performance. When enabled, SELinux may block the kernel module request.
Audit log:
SELinux is preventing /usr/sbin/nginx from module_request access on the system labeled kernel_t
Fix:
setsebool -P domain_kernel_load_modules on
Note: On RHEL 9+ and Rocky Linux 10, this boolean is already enabled by default.
9. PageSpeed or ModSecurity Startup Failure
These modules require executable memory for JIT compilation. Without the proper NGINX SELinux policy, NGINX fails to load the module:
dlopen() "/etc/nginx/modules/ngx_pagespeed.so" failed: cannot enable executable stack
as shared object requires: Permission denied
Quick fix:
setsebool -P httpd_execmem on
Better approach: Use the GetPageSpeed repository, which automatically installs SELinux policy modules with each NGINX module package. For example, installing nginx-module-security automatically pulls in nginx-module-security-selinux.
10. Database Connections from PHP
PHP scripts connecting to MySQL or PostgreSQL through NGINX/PHP-FPM:
setsebool -P httpd_can_network_connect_db on
11. NFS-Mounted Web Content
Serving files from NFS shares:
setsebool -P httpd_use_nfs on
For CIFS/Samba mounts:
setsebool -P httpd_use_cifs on
Creating Custom NGINX SELinux Policy Modules
When no existing boolean or file context rule solves your problem, you can create a custom policy module. This is rare but necessary for specialized NGINX modules.
Step 1. Generate a policy from audit log denials:
grep nginx /var/log/audit/audit.log | audit2allow -M nginx_custom
This creates nginx_custom.te (policy source) and nginx_custom.pp (compiled policy). You can then install it directly with semodule -i nginx_custom.pp.
Step 2. Review the .te file before installing. Never blindly trust audit2allow output — it may grant more permissions than necessary. The generated .te file often includes comments suggesting a boolean instead:
#!!!! This avc can be allowed using one of these booleans:
# httpd_can_network_connect, nis_enabled
Always prefer enabling a boolean over installing a custom policy module.
Step 3. If you need to write a custom .te file from scratch, the module name must match the output filename. Here is an example custom policy for the NGINX ipset-access module, which needs netlink socket access for kernel ipset communication:
module nginx_module_ipset_access 1.1;
require {
type httpd_t;
class netlink_netfilter_socket { bind create getattr getopt read setopt write };
class capability { net_admin };
}
allow httpd_t self:netlink_netfilter_socket { bind create getattr getopt read setopt write };
allow httpd_t self:capability { net_admin };
Compile and install:
checkmodule -M -m -o nginx_module_ipset_access.mod nginx_module_ipset_access.te
semodule_package -o nginx_module_ipset_access.pp -m nginx_module_ipset_access.mod
semodule -i nginx_module_ipset_access.pp
Step 4. Verify installation and manage modules:
# List custom modules
semodule -l | grep nginx
# Remove a custom module
semodule -r nginx_module_ipset_access
SELinux and PHP-FPM
PHP-FPM runs under the same httpd_t SELinux domain as NGINX. Check for PHP-FPM specific violations:
grep php-fpm /var/log/audit/audit.log | audit2why
The most common PHP-FPM SELinux issue is write access. When PHP-FPM cannot write to web directories in enforcing mode, relabel the directories with the writable type:
semanage fcontext -a -t httpd_sys_rw_content_t "/var/www/vhosts/(.*)/httpdocs/wp-content(/.*)?"
restorecon -Rv /var/www/vhosts/*/httpdocs/wp-content
For the full list of file types relevant to web servers, consult man httpd_selinux.
nginx.org Package Bug
A known bug exists in the base NGINX package from nginx.org. Fix it with a systemd override:
systemctl edit nginx
Add:
[Service]
ExecStartPre=/usr/bin/rm -f /run/nginx.pid
ExecStartPre=/usr/sbin/nginx -t
This issue does not affect NGINX installed from EPEL or the GetPageSpeed repository.
Run Secure
Once you have resolved all NGINX SELinux denials, disable permissive mode for full enforcement:
semanage permissive -d httpd_t
Verify that SELinux is enforcing:
getenforce
Should output Enforcing.
Your NGINX installation is now running with proper SELinux protection. For the complete SELinux HTTP policy reference, run man httpd_selinux.

Kelsey
When I tried the command “yum install policycoreutils-python,” I got an error: “Error: Unable to find a match: policycoreutils-python”. Some searching led me to understand that the package has been renamed, so the command should read:
yum install policycoreutils-python-utils
(Note the change is just adding “utils” to the end of the package.)
This worked for me.