Troubleshoot Nginx 403 responses across a site

Use the error log to separate filesystem permissions, SELinux, path mapping, and access rules before changing ownership.

Best practices
Illustration of server operations and request logs

When every file returns 403, start with one failing request and its error-log entry. The status alone cannot tell you whether Nginx, PHP, or another upstream denied access.

sudo tail -50 /var/log/nginx/error.log

Permission denied suggests filesystem or security-policy checks. directory index ... is forbidden points to directory handling. access forbidden by rule points to Nginx access rules.

Check the worker's access to the whole path

ps aux | grep '[n]ginx: worker'
namei -l /var/www/html/index.html

The worker needs search permission on each parent directory and read permission on the target file. A site beneath a private home directory may be blocked before Nginx reaches the otherwise readable file.

For an intentionally public, static-only document tree, conventional permissions are:

sudo find /var/www/html -type d -exec chmod 755 {} \;
sudo find /var/www/html -type f -exec chmod 644 {} \;

Apply this only after confirming that the tree contains public content and no files requiring more restrictive modes. Keep deployment ownership with the deployment account; Nginx generally does not need ownership or write permission. A dedicated read-only group or ACL can provide narrower access than world-readable modes.

Check SELinux without disabling it

getenforce
ls -Z /var/www/html/
sudo ausearch -m AVC -ts recent

For a nonstandard public-content directory, a persistent context mapping can look like:

sudo semanage fcontext -a -t httpd_sys_content_t '/srv/site(/.*)?'
sudo restorecon -Rv /srv/site

Use -m when modifying an existing mapping. restorecon applies the policy's expected labels; it does not invent a mapping for an arbitrary location. Diagnose audit denials rather than turning enforcement off as a routine fix.

Verify root, alias, and access rules

Inspect the complete configuration with nginx -T, then compare the logged filesystem path with the deployed files and symlink targets. A nonexistent file or directory normally produces a 404 in straightforward static serving; a wrong root is not guaranteed to produce a 403.

Also inspect inherited access rules:

location / {
    allow 192.168.1.0/24;
    deny all;
}

This deliberately rejects clients outside the allowed network. Behind a proxy, verify the trusted-client-IP setup before changing allowlists.

Retest a page, a static asset, and an intentionally restricted path. A fix should restore allowed requests without making restricted content public. Guance HTTP synthetic tests can check expected endpoint responses; collected access logs can show which URIs still return 403.

FAQ

Will chmod 777 fix it?

It may remove a Unix permission barrier while unnecessarily granting write access. It cannot fix a wrong path, an Nginx deny rule, or SELinux policy. Correct the actual access requirement instead.

What if only PHP requests fail?

Inspect the matched PHP location, FPM logs, script permissions, and FPM restrictions such as security.limit_extensions. Static-file success does not rule out permissions on different PHP files or directories. Socket connection failures commonly produce 502, so correlate the actual logs.

Does the same approach apply in a container?

Yes, but inspect numeric UIDs/GIDs, mount options, parent paths, and security labels from inside the container. Do not change the container's runtime user blindly; grant the intended worker the required access to the mounted content.

References: Nginx request processing, SELinux configuration for nonstandard paths.

Get a tailored plan

Contact us

Join the community

Scan with WeChat
to join the community

Try Guance

Start online and pay only for what you use.

Get started

Choose a Guance plan

Code hosting