Fix an Nginx virtual-host permission error by tracing one request to the exact server block and filesystem path, then granting the Nginx worker only the directory traversal and file-read access it needs. Do not start with chmod -R 777: a wrong server_name, root, location, worker account, ACL, or SELinux context can produce the same symptom.
Need a server to try this on? Antihost offers Linux VPS plans with NVMe storage, full root access and a 14-day free trial.
The reliable sequence is: identify the selected virtual host, resolve the URI to a real path, confirm the loaded configuration, inspect every directory component and the worker identity, correct Unix permissions or ACLs, check mandatory access control, and retest with logs.
As an Amazon Associate I earn from qualifying purchases.
Quick wins for a faster PC:
Clear out junk files and repair common Windows errorsFree Scan →Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Repair Windows errors before they cause bigger problemsFix Now →What Nginx must be able to access
For a static request, Nginx maps the request URI to a filesystem path and opens the resulting file. The server_name rules select the virtual-server block for the request’s Host header. The root directive supplies a base directory, and Nginx appends the URI to that directory. A more specific root inside a location can change the result. An alias uses a different path-construction rule and is a frequent source of surprises.
Unix access is checked one path component at a time. The worker needs the execute (x) permission on every directory from the filesystem root to the target directory; that permission means “traverse,” not “run a program.” It then needs read (r) permission on the file. Ownership, group membership, mode bits, and ACL entries all matter. The account is not necessarily www-data or nginx; verify the account used by your package or build.
#1 Best Overall
A request for a URI ending in a slash starts index-file lookup. The default index name is index.html unless your configuration changes it. If the directory is reachable but no configured index exists, the result is commonly a 404 or a directory-index response, not the same filesystem error as a denied file open.
1. Capture the exact failure before changing permissions
Reproduce the problem with the intended host name, then watch the Nginx error log while making one request. Common package locations are /var/log/nginx/error.log and /var/log/nginx/access.log; a custom build or systemd journal may use another location.
Free tools Windows power users keep installed
One-click scans. No signup required.
sudo tail -f /var/log/nginx/error.log
In another terminal, send a request that includes the host header used by the virtual host:
curl -i -H 'Host: example.com' http://127.0.0.1/
For HTTPS, use the real name so certificate and server-name selection are representative:
curl -ik https://example.com/
Look for the path Nginx attempted and the errno text. A message such as “permission denied” identifies an access check that failed, but the path in that message is the key evidence: it tells you which directory or file you must inspect. A 404 without a permission errno points first to URI-to-path mapping, a missing file, or index selection. A 403 can also come from directory-index settings or a location rule, so do not assume every 403 is a Unix mode problem.
2. Confirm which virtual host handles the request
Fixing the permissions of the wrong site directory changes nothing. The request’s host header, listen address, port, and server_name matching determine the selected block. DNS may point the name at a different machine, and a request made by IP can select a default server instead of your new block.
Crashes, No Sound, or Screen Glitches?
Random freezes, missing sound and display glitches usually trace back to one bad driver. Find and replace yours safely.Free scan · under a minutePC Slower Than It Used to Be?
A free scan shows the junk files, broken settings and background clutter dragging Windows down - then fixes them in one click.Free scan · Windows 10 & 11Check the name and port
- Verify the browser or client is requesting the same fully qualified name listed in
server_name. - Check whether the request is HTTP or HTTPS and whether the block listens on the corresponding port.
- When testing locally, use
curl -H 'Host: name' http://127.0.0.1/so DNS does not obscure server selection. - Inspect for duplicate or wildcard names and for a
default_serverblock that catches unmatched requests.
Inspect the matching server and location
Read the configuration files included by the active top-level configuration. Find the server containing the relevant server_name, then inspect all nested locations that could match the URI. A location-level root overrides the server-level value for requests in that location. A regular-expression location can win over a prefix location, and an alias maps the location to a directory or file without simply appending the complete URI.
Rank #2
For a request such as /images/logo.svg with root /var/www/example.com/public;, the ordinary mapping is /var/www/example.com/public/images/logo.svg. That is only a starting point: a more specific location, an alias, or a rewrite can produce another path. Use the path printed in the error log as the final authority for the failing request.
3. Verify the configuration Nginx actually loaded
Editing a file does not change the running configuration until Nginx successfully tests and reloads it. Package layouts vary, so first find the compiled configuration path:
nginx -V 2>&1
Look for the --conf-path value in the output. Follow its include directives to locate the virtual-host file you are editing. Then test the complete configuration:
sudo nginx -t
Only after a successful test should you reload:
sudo systemctl reload nginx
If your system does not use systemd, use the service manager supplied by that distribution. A failed test leaves the previous working configuration in place; read the reported file and line number instead of changing permissions to compensate for a syntax or include error.
4. Identify the real Nginx worker account
The master process may start as root while workers run as an unprivileged account. The user directive in the loaded configuration usually sets that account, but confirm the running processes because a custom service or build can differ.
ps -eo user,pid,ppid,cmd | grep '[n]ginx'
Also inspect the effective configuration for a user directive and note whether it names a user, a group, or both. Record the worker user and group before changing ownership. Granting access to an assumed account can leave the actual worker blocked, while changing ownership to the wrong account can give an unrelated service access to your files.
5. Inspect every component of the failing path
Use the exact path from the error log, not a path you expected from the configuration. namei -l displays each component’s owner, group, and mode:
Recommended Free Tools
namei -l /var/www/example.com/public/index.html
For filesystems without namei, inspect each directory explicitly:
ls -ld /var /var/www /var/www/example.com /var/www/example.com/public
ls -l /var/www/example.com/public/index.html
getfacl /var/www/example.com/public/index.html
Check the following at every level:
- Each parent directory has execute permission for the Nginx worker through its owner, group, or an ACL entry.
- The final directory is traversable and, when Nginx must list it, also readable; directory listing is a separate configuration choice and is not required to serve a known file.
- The target file is readable by the worker’s effective user or one of its groups.
- An ACL does not contain a restrictive mask or explicit deny that overrides the mode-bit assumption.
- Symlinks, if present, lead to a target whose own parent directories are traversable. A readable link does not bypass permissions on its target.
6. Grant the minimum required Unix access
Keep ownership with the deployment or site owner whenever possible. Nginx normally needs to read and traverse content, not write it. Choose a group or ACL when a separate deployment account must update files; do not make the web worker the owner merely because it serves the files.
Simple static-site layout
For a single administrator-owned site, a common starting point is directory mode 755 and regular-file mode 644. These are examples, not universal requirements: preserve tighter modes where a group or ACL provides the needed access, and do not apply one mode indiscriminately to sockets, private files, or executable directories.
sudo find /var/www/example.com/public -type d -exec chmod 755 {} ;
sudo find /var/www/example.com/public -type f -exec chmod 644 {} ;
Run those commands only after confirming that every item under the tree is intended to be public static content. A deployment may contain secrets, upload directories, or executable scripts that require different handling.
Group-based deployment access
If a deployment account must write the site while Nginx only reads it, create or use a dedicated shared group, add the relevant accounts, and set group ownership on the publish tree. Ensure directories grant group traversal and files grant group read access. Set the set-group-ID bit on directories when you want newly created files and subdirectories to inherit the site group:
sudo chgrp -R webpublish /var/www/example.com/public
sudo find /var/www/example.com/public -type d -exec chmod 2750 {} ;
sudo find /var/www/example.com/public -type f -exec chmod 640 {} ;
The numeric modes above assume the Nginx worker is a member of webpublish and that “other” users should have no access. Add the worker to the group only if that is part of your security design, then restart or otherwise refresh the service’s group credentials as required by the operating system.
Use an ACL for a narrowly scoped exception
When changing the owning group would affect other workflows, grant the worker a named ACL entry on the necessary directories and files. Apply a default ACL to a publish directory only when future files should inherit the same rule:
Rank #4
sudo setfacl -m u:nginx:--x /var/www/example.com
sudo setfacl -m u:nginx:--x /var/www/example.com/public
sudo setfacl -m u:nginx:r-- /var/www/example.com/public/index.html
getfacl /var/www/example.com/public/index.html
Replace nginx with the verified worker account. The example grants only traversal on the parent directories and read access to one file; a real site needs equivalent entries for all files Nginx must serve. Check the ACL mask shown by getfacl, because it can limit named-user and group entries.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Why chmod -R 777 is not a repair
Mode 777 grants every local user read, write, and traversal access. It can expose uploads, allow tampering with served files, and still fail when a mandatory access-control policy denies the path. Fedora’s Nginx guidance specifically warns against recursive world-writable permissions. Correct the selected account, path, and policy instead.
7. Check SELinux when Unix permissions look correct
On SELinux-enabled systems, a file can be readable according to owner, group, and mode bits while the security policy still denies Nginx. This is especially common when you place a virtual host under an unusual directory instead of the distribution’s standard web root.
Inspect the security context:
ls -Zd /var/www/example.com /var/www/example.com/public
ls -Z /var/www/example.com/public/index.html
If the error log and audit log show a denial, follow the distribution’s policy procedure for a persistent file-context rule, then restore contexts. A typical workflow uses the policy tools supplied by the distribution:
sudo semanage fcontext -a -t httpd_sys_content_t '/var/www/example.com/public(/.*)?'
sudo restorecon -Rv /var/www/example.com/public
Use the label appropriate to the operation: content Nginx only reads is different from directories that a web application must write. Do not copy a context from an unrelated path without checking the local policy documentation. If semanage is unavailable, install the distribution package that provides it or use the platform’s documented equivalent.
8. Understand 404, 403, and permission-denied results
| Observed result | What to check first | Typical correction |
|---|---|---|
| 404 and no permission errno | Effective root/alias, URI spelling, rewrites, missing file, or index name |
Correct the path or create/configure the intended index; use try_files for an explicit existence check |
| 403 with directory URI | Index lookup, directory listing policy, location rules, and directory traversal | Provide the configured index file or deliberately configure directory handling; fix traversal if the log reports denial |
| “Permission denied” in error log | Every parent directory, target-file access, ACL mask, and SELinux context | Grant the verified worker the minimum traversal/read access |
| Works for the default site but not the new host | Host-header selection, duplicate names, and the new block’s root | Test with the correct Host, inspect the selected block, and reload the tested configuration |
Use try_files when you want Nginx to check candidate paths and return a deliberate fallback:
location / {
try_files $uri $uri/ =404;
}
This does not grant filesystem access; it makes path existence and fallback behavior explicit after access checks can be performed.
Best Value
9. A complete, conservative virtual-host example
The following block serves static files from a dedicated public directory, declares an index file, and returns 404 when neither the requested file nor directory exists:
server {
listen 80;
server_name example.com www.example.com;
root /var/www/example.com/public;
index index.html;
location / {
try_files $uri $uri/ =404;
}
}
With this configuration, a request for /docs/guide.html normally maps to /var/www/example.com/public/docs/guide.html. A request for / causes index lookup in the public directory. If another location defines its own root or an alias, that location’s mapping takes precedence for matching requests.
Do these 3 things before closing this tab:
1Clear out junk files and repair common Windows errors2Scan for outdated or missing drivers - takes under a minute3Repair Windows errors before they cause bigger problemsAfter saving the file, run sudo nginx -t, reload only after it succeeds, make a request with the correct host header, and watch the error log. The log path and errno should now correspond to the permissions you inspected rather than to an unexpected directory.
10. Troubleshooting branches for persistent failures
The log path is not the directory you edited
Re-check the request’s host header and the selected server_name. Search included configuration files for another matching block, a regex location, an alias, or a rewrite. Confirm that DNS and the listening port reach the intended machine.
The file is readable but a parent directory is denied
Run namei -l and fix the first path component lacking execute permission for the worker. Grant traversal narrowly on that directory; do not make the entire tree world-writable.
Changing the mode had no effect
Verify the running worker account, inspect ACLs with getfacl, and check SELinux contexts and audit entries. A mode change cannot override an ACL mask or a mandatory policy denial.
The site works after a reboot but fails after deployment
Compare ownership, modes, ACLs, and contexts on newly deployed files. Deployment tools may create files under a different group, remove inherited ACLs, or publish a new symlink whose target is outside the permitted tree. Make the deployment process preserve the intended group, default ACL, and security context.
Only directory URLs fail
Check whether the trailing-slash request triggers index lookup. Confirm the configured index name and that the file exists in the effective directory. If directory listing is intentionally disabled and no index exists, a 403 or 404 can be expected depending on the location configuration.
Quick Recap
11. Final verification checklist
- Reproduce the request with the intended host name and port.
- Read the error log and copy the exact filesystem path and errno.
- Identify the matching
serverandlocation; account forroot,alias, and rewrites. - Find the loaded configuration with
nginx -V 2>&1, inspect included files, and runsudo nginx -t. - Verify the worker user and group from the configuration and running processes.
- Run
namei -land inspect ACLs for every path component. - Grant only the required directory traversal and file-read rights, retaining deployment ownership.
- On SELinux systems, inspect labels and audit denials; add a persistent context rule when necessary.
- Reload only after a successful configuration test.
- Request the URL again, confirm the status code, and ensure the error log no longer reports the denied path.
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.




