Driver FixRecommendedSound, Wi-Fi or graphics acting up? Check drivers firstFind missing or outdated drivers fast.Check DriversOctober DealsAmazon USOctober deal check: compare before you payAmazon US: current deals, useful picks and tech finds.Check DealsWindows FixRecommendedWindows errors stealing your time? Find the fix fastScan stability, cleanup and performance issues.Fix Now×
Skip to content
VGSources
Blog

How to Fix Nginx Virtual Host Directory Permissions (403 and “Permission Denied”)

A practical, security-conscious workflow for Nginx virtual-host 403 and permission-denied errors: identify the active block, resolve the real path, inspect traversal rights, repair ownership or ACLs, check SELinux, and retest.
Length11 min Posted Quest giverVGSources Team
Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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.

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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.

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Check 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_server block 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.

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:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
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:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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:

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

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.

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

After 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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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.

11. Final verification checklist

  1. Reproduce the request with the intended host name and port.
  2. Read the error log and copy the exact filesystem path and errno.
  3. Identify the matching server and location; account for root, alias, and rewrites.
  4. Find the loaded configuration with nginx -V 2>&1, inspect included files, and run sudo nginx -t.
  5. Verify the worker user and group from the configuration and running processes.
  6. Run namei -l and inspect ACLs for every path component.
  7. Grant only the required directory traversal and file-read rights, retaining deployment ownership.
  8. On SELinux systems, inspect labels and audit denials; add a persistent context rule when necessary.
  9. Reload only after a successful configuration test.
  10. 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.

Leave a Reply

Your email address will not be published. Required fields are marked *

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

More quests from Patch Notes

  1. How to Set Up a RedM (RDR2) Server in 2026: License Key, txAdmin and server.cfg, Step by StepBlog11min
  2. Best RedM (RDR2) Server Hosting in 2026: Comparing Four Hosts on Slots, Memory and PriceBlog10min
  3. How to Host a Mindustry Server in 2026: server-release.jar, Port 6567 and the Commands That MatterBlog7min
Recommended PC Tool
Recommended PC Tool
PC Slower Than It Used to Be?Free scan - under a minute
Outdated Drivers Are Slowing You DownFree scan - exact matches

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.