How to fix SELinux permission denied errors with Nginx config files
Of course, we all check our config files with nginx -t before we start Nginx, right? But that might not be enough if you have SELinux installed on your server. Ever ran into a ” ermission denied” error when trying to start or reload a Nginx server? It’s something that I ran into a couple of times, so time to blog about it.
This issue is usually related to SELinux file contexts, which can quietly block access even when everything else looks right. This guide will walk you through diagnosing and fixing the problem, especially when dealing with custom or non-standard Nginx config files.
The Problem
You’ve added a new Nginx config file, maybe something like:
/etc/nginx/conf.d/my-site.conf
You try to reload Nginx (systemctl reload nginx
), but you get this error:
nginx: [emerg] open() "/etc/nginx/conf.d/my-site.conf" failed (13: Permission denied)
Permissions on the file look fine (nginx process needs read rights), so what’s the problem?
SELinux
SELinux (Security-Enhanced Linux) uses file contexts to enforce stricter access control than traditional file permissions. Even if your file is rw-r--r--
and owned by the right user, SELinux may deny access if the context isn’t what it expects.
To see the SELinux context, use:
ls -Z /etc/nginx/conf.d/my-site.conf
The -Z flag is the important piece of the puzzle here. This flag shows the SELinux security context attached to each file. It’s crucial for debugging access issues on SELinux-enabled systems. You might see output like:
unconfined_u:object_r:user_home_t:s0
You see 4 parts here, split by a ‘:’. Here’s what each part means:
- user: The SELinux user who owns the object. Common values include
system_u
(for system files) andunconfined_u
(often default for user-created files). This matters —unconfined_u
can sometimes be blocked depending on policy. - role: The role of the object. For files, this is almost always
object_r
. - type: This is the most important field. It determines what the file is, and what processes can interact with it. For Nginx config files, this must be
httpd_config_t
. - level: This is the MLS/MCS level. Typically
s0
on most systems unless multi-level security is used.
So in the example above, both the unconfined_u and the user_home_t are problematic.
Fixing the context
Step 1: Check the correct context
First check what the correct context should be. The nginx.conf file or nginx.conf.default file should be good examples:
ls -Z /etc/nginx/nginx.conf
Expected output:
system_u:object_r:httpd_config_t:s0
SELinux policies only allow Nginx to read files labeled as httpd_config_t
. If your custom file isn’t labeled correctly, Nginx will be blocked — even as root.
Step 2: Install SELinux Tools (if needed)
Check if semanage is on your system (which semanage
). If it’s not, you need to install it:
# For RHEL, CentOS, Fedora:
dnf install policycoreutils-python-utils
# For Debian/Ubuntu:
sudo apt install policycoreutils
Step 3: Set the Correct File Context
semanage is used to set context rules:
semanage fcontext -a -t httpd_config_t -s system_u "/etc/nginx/conf.d(/.*)?"
(or semanage fcontext -a -t httpd_config_t -s system_u "/etc/nginx/sites-enabled(/.*)?"
if your config files are located in sites-enabled)
Step 4: Apply the New Context
Apply the change using:
restorecon -Rv /etc/nginx/conf.d/
where the -v
flag stands for “verbose” and shows you exactly what changes were made and -R
applies the changes recursively, so all files and folders inside the target directory are updated.
sample output might look like:
Relabeled /etc/nginx/conf.d/my-site.conf from unconfined_u:object_r:user_home_t:s0 to system_u:object_r:httpd_config_t:s0
Step 5: Reload nginx
The final step will be to reload (or start) nginx. Of course, you first check your config, using:
nginx -t
before you reload
systemctl reload nginx
or start
systemctl start nginx
Obviously, use sudo if you’re not root.
Conclusion
When Nginx throws a mysterious “Permission denied” error despite your config file looking perfect, SELinux is usually the missing piece of the puzzle.
- Use
ls -Z
to check the SELinux context of your config files. - Make sure the type is
httpd_config_t
and user issystem_u
. - Use
semanage fcontext
andrestorecon
to permanently fix the issue.