systemctl status nginx.service shows 'Permission denied' with no clear error

SELinux blocking Nginx start on RHEL 9 — fix in 60 seconds

Linux & Unix Beginner 👁 14 views 📅 Jun 16, 2026

If Nginx won't start on RHEL 9 or Fedora and logs just say 'permission denied', SELinux is almost certainly the culprit. The fix is a single command to set the right context.

You've spent 20 minutes checking permissions, re-reading config files, and Nginx still refuses to start with a vague 'permission denied'. It's SELinux. Every time. Let's fix it now.

The Fix

  1. Check if SELinux is the problem: look at the audit log for denials.
    sudo ausearch -m avc -ts recent
    If you see something like denied { open } for pid=... comm="nginx", we're on the right track.
  2. Find the offending file context. Usually it's your web root — /var/www/html or wherever you point root in your Nginx config. Run:
    ls -Z /var/www/html/index.html
    If you see user_home_t or var_t instead of httpd_sys_content_t, that's the problem.
  3. Fix the file context recursively:
    sudo restorecon -R /var/www/html
    Then restart Nginx:
    sudo systemctl restart nginx
    It should come right up.

If it still doesn't work after that, check your Nginx error log at /var/log/nginx/error.log. If you see connect() to unix:/var/run/php-fpm.sock failed, that's a socket context issue — see the variations section below.

Why This Works

What's actually happening here is that SELinux enforces a type-enforcement policy on every file and process. When you copy files into /var/www/html from a home directory, a USB stick, or a tarball, those files inherit the context of their source — often user_home_t or var_t. Nginx runs under the httpd_t domain (its SELinux security context). The policy says: processes in httpd_t can only read files labeled httpd_sys_content_t (or a few others like httpd_sys_rw_content_t for write access). Any other label results in a denial, logged to audit.log, and Nginx gracefully fails to serve that file — or outright fails to start if its own config file is mislabeled.

restorecon resets the SELinux context of a file to the default for its path. The default for /var/www/html is httpd_sys_content_t, because the SELinux policy defines that path as a web content directory. Running restorecon -R applies that rule recursively. After that, Nginx can read everything inside.

The reason step 3 works is that restorecon doesn't care about Linux file permissions — it only touches the SELinux labels. So if you already fixed Unix permissions (e.g., chmod 755 or chown nginx:nginx) and it still fails, you were missing this step.

Less Common Variations

Nginx won't start after changing its config file location

If you moved nginx.conf to /etc/nginx/custom/, SELinux may not allow Nginx to read it. The context should be etc_t. Run:

sudo restorecon -R /etc/nginx/custom/

Or, if you need a custom context, use semanage fcontext, but that's overkill for most setups.

PHP-FPM socket permission denied

If Nginx starts but returns 502 Bad Gateway, and the error log mentions a Unix socket like /var/run/php-fpm.sock, SELinux may be blocking the connection. PHP-FPM runs under httpd_sys_script_t (if you're using the default policy). The socket needs the httpd_sys_content_t label or a custom one. Quick check:

ls -Z /var/run/php-fpm.sock

If it shows var_run_t, run:

sudo semanage fcontext -a -t httpd_sys_rw_content_t "/var/run/php-fpm\.sock"
sudo restorecon -v /var/run/php-fpm.sock
sudo systemctl restart php-fpm nginx

This tells SELinux that the socket file is a writable web content object, so httpd_t can connect to it through the Unix domain socket mechanism.

Custom web root on a different filesystem (like /data/web)

If your web root is on a separate mount, restorecon might not apply the default context because the path doesn't match any predefined SELinux file context. You'll get no label change. In that case, you need to add a new mapping:

sudo semanage fcontext -a -t httpd_sys_content_t "/data/web(/.*)?"
sudo restorecon -R /data/web

This permanently tells SELinux: anything under /data/web should get the httpd_sys_content_t label. After that, Nginx can read files there.

Prevention

Stop copying files manually into web directories. Use a deployment tool (rsync, ansible, or even a simple script) that runs restorecon after copying. Better yet, set up a cron job that runs restorecon -R /var/www/html every hour — overkill, but it works.

Really though, the cleanest way is to add a restorecon call to your CI/CD pipeline or post-receive hook. For example, in a Git post-receive hook on the server:

#!/bin/bash
GIT_WORK_TREE=/var/www/html git checkout -f
restorecon -R /var/www/html

That way every deploy resets contexts automatically. You won't see this error again.

If you find yourself constantly fighting SELinux, read the audit2why output — it literally tells you the exact command to fix the denial. Run sudo audit2why -a after a failure. That tool was written by someone who knew you'd be annoyed.

Was this solution helpful?