A 403 Forbidden response from nginx means the server understood the request but refused to carry it out — unlike 404, the file or route is found, but access to it is closed. The breakdown below is aimed at an administrator who sees 403 in the browser or in the logs and wants to find the cause quickly, instead of guessing directives at random.
What the 403 code means in nginx logs
In access.log, a 403 entry appears next to all other requests, but nginx does not always explain the cause in that line — for details, check error.log, which usually names the exact file or directive behind the refusal.
403 differs from 401: with 401 the browser shows a login prompt, with 403 it is simply a refusal without any authorization request. These are different mechanisms, and confusing them during diagnosis does not help.
The most common cause — index not found and autoindex off
If a location has no file named by the index directive — usually index.html or index.php — and autoindex is off (the default), nginx returns 403 instead of a directory listing. That is protective behavior, not a configuration mistake.
Checking it is simple: look for the needed file at the root of the location and check the index value in the config. Basic setup is covered in the article on configuring nginx.
File and directory permissions
The nginx process runs as a dedicated user — most often www-data or nginx — and that user needs read access to the file and execute access (not read!) to every directory along the path to it. That is a common cause of 403: the file itself is readable, but one of the parent directories is closed to outsiders.
| Item | Needed permission | Common mistake |
|---|---|---|
| File at the site root | read for the process owner | mode 600 for another user only |
| Site directory | execute for traversal | mode 700 with no group or other |
| User home directory | execute for group or other | mode 750 blocks nginx access |
| Uploaded files | read after a script uploads them | file created with mode 600 |
| PHP-FPM socket | access for the nginx group | socket owned only by php-fpm |
Allow and deny directives and IP restrictions
The allow and deny directives restrict access by client address and often stay in the config after a temporary block — for example, an administrator closed access during maintenance and forgot to lift the restriction.
Rules are read top to bottom, and an implicit deny all at the end of a block is a common reason a given IP does not match any allow rule. Similar restrictions are covered in the article on hardening nginx, which explains how not to lock yourself out.
Configuration conflicts between several location blocks
When two location blocks match the same path, nginx picks by priority rules — exact match first, then the longest prefix match, then a regular expression. If a broader block happens to contain a deny directive, it overrides the more specific block that was meant to apply.
Diagnosing such conflicts is easiest by reading the full expanded configuration rather than a single file: included directives turn a simple-looking setup into several levels of nesting.
How to find the cause and fix it
The diagnostic order that covers most cases:
- look at the exact line in error.log — it names the file or directive behind the refusal;
- check permissions on the file and on every directory along the path with namei -l;
- check against the expanded config with nginx -T to see all includes at once;
- temporarily remove the allow and deny directives and check whether the error disappears;
- reload the configuration with nginx -s reload after the fix and check the request again.
sudo tail -n 20 /var/log/nginx/error.log
sudo namei -l /var/www/site/public/index.html
sudo nginx -t
sudo nginx -s reload
After fixing the cause, it is worth checking whether buffer and cache settings cause similar errors under growing load — that is covered in the article on optimizing nginx on a VPS.
Frequently asked questions
How does 403 differ from 404 in an nginx response?
404 means the server did not find the resource at the given path at all. 403 means the opposite: the resource exists, but the server deliberately refuses access — because of file permissions, allow and deny directives, or a missing index file with autoindex off.
Why does 403 happen if the file itself is readable but a directory above it is closed?
Traversing a directory needs execute permission, not read permission. If one of the parent directories along the path lacks that permission for the user nginx runs as, the server cannot reach the file even if the permissions on the file itself are correct.
Does autoindex need to be enabled to get rid of 403?
Not necessarily, and usually it should not be: autoindex opens the directory file listing to every visitor, which is itself a risk for private data. It is better to add the missing index file or point the location at the right path explicitly.
Can 403 appear because of SELinux rather than nginx itself?
Yes, on systems with SELinux enabled, a file's context is checked separately from regular Linux permissions: even correct read permissions will not help if the context label does not allow access to the nginx process. In that case the refusal shows up in the SELinux audit log, not only in the nginx error.log.