Nginx 403 Forbidden: 6 Causes and How to Fix Them

Nginx 403 Forbidden means Nginx found the request but won't serve it: usually file permissions, a missing index file or a deny rule. The log says which.
A 403 is frustrating because everything looks right. The files are there, the domain resolves, Nginx is running, and still the browser says "403 Forbidden". The good news is that Nginx always writes the reason to its error log, and there are only a handful of reasons. I set up Nginx for every site I deploy, including this one, and the 403s I've fixed for clients almost always came down to the first three causes below. Here's how to find yours in two commands.
Key takeaways
- Read the log:
sudo tail -n 20 /var/log/nginx/error.log. "directory index ... is forbidden" and "(13: Permission denied)" are two different problems with two different fixes. - Nginx needs execute permission on every parent folder, not only read permission on the file.
namei -lshows the whole path at once. - Files in
/home/userare the classic trap: home folders are private by default on modern Ubuntu, so Nginx can't walk into them. - A folder without
index.htmlreturns 403 rather than 404, because listing directories is off by default. - Never fix a 403 with
chmod 777. Give the Nginx user exactly the access it needs.
Step 1: Find the reason in the error log
sudo tail -n 20 /var/log/nginx/error.log # reload the page in the browser, then look again, or follow it live: sudo tail -f /var/log/nginx/error.log
You'll see one of these:
directory index of "/var/www/shop/" is forbidden open() "/home/deploy/site/index.html" failed (13: Permission denied) access forbidden by rule, client: 203.0.113.7
No log line at all? Then the 403 isn't coming from your Nginx. Check the response headers with curl -sI https://example.com. If they say server: cloudflare, a Cloudflare WAF or security rule blocked the request, not your server.
Cause 1: "directory index of ... is forbidden"
Nginx was asked for a folder, found no index file in it, and directory listing is off. So it refuses. Check what's actually in the folder and what your config expects:
ls -la /var/www/shop/ grep -nE 'root|index|try_files' /etc/nginx/sites-enabled/shop
Common reasons:
- The build went somewhere else. Your React or Vite app builds to
dist/butrootpoints at the project folder. Pointrootat the folder that containsindex.html. - PHP site without
index.phpin the index list. Useindex index.php index.html;. - A single-page app needs
try_files $uri $uri/ /index.html;so deep links fall back to the app. - The deploy hasn't finished or the upload failed, so the folder is empty.
Cause 2: "(13: Permission denied)"
The file exists, but the Nginx worker can't read it. On Ubuntu and Debian Nginx runs as www-data; on RHEL-family systems, as nginx. To read /home/deploy/site/index.html, that user needs execute (x) on every folder in the path and read (r) on the file. One locked folder anywhere in the chain is enough for a 403. This command shows them all:
sudo namei -l /home/deploy/site/index.html
f: /home/deploy/site/index.html drwxr-xr-x root root / drwxr-xr-x root root home drwxr-x--- deploy deploy deploy <- www-data can't enter drwxr-xr-x deploy deploy site -rw-r--r-- deploy deploy index.html
That middle line is the problem I meet most. Since Ubuntu 21.04, new home folders are created with mode 750, so other users, including www-data, can't pass through. Two clean fixes:
# Best: serve from /var/www, owned by your deploy user, readable by all
sudo mkdir -p /var/www/site
sudo chown -R deploy:deploy /var/www/site
sudo find /var/www/site -type d -exec chmod 755 {} +
sudo find /var/www/site -type f -exec chmod 644 {} +
# Or: let only the www-data group pass through the home folder
sudo chgrp www-data /home/deploy
sudo chmod 750 /home/deploy # owner rwx, group r-x, others nothing
Test as the Nginx user, which is the real proof:
sudo -u www-data cat /home/deploy/site/index.html > /dev/null && echo readable
Cause 3: "access forbidden by rule"
Your own config blocked the request with deny. Look for allow and deny lines, including in included snippets:
sudo nginx -T 2>/dev/null | grep -nE 'allow|deny'
A common one is an admin area locked to an office IP that changed, or a location ~ /\. rule that also blocks /.well-known/, which breaks Let's Encrypt validation. Add an explicit exception above it: location ^~ /.well-known/acme-challenge/ { allow all; }.
Cause 4: SELinux (Rocky, Alma, RHEL, Fedora)
If permissions look perfect and you're on a Red Hat family system, SELinux is the likely cause. Files you created in your home folder or copied with mv keep the wrong security label. Check and fix the label:
ls -Z /var/www/site sudo ausearch -m avc -ts recent | tail sudo semanage fcontext -a -t httpd_sys_content_t "/var/www/site(/.*)?" sudo restorecon -Rv /var/www/site
Don't disable SELinux to make a 403 go away. Relabelling takes a minute and keeps the protection.
Cause 5: Wrong root or alias
A path mistake can produce a 403 instead of a 404 when it lands on a real folder. The classic is alias without a trailing slash, or using root where you meant alias. With location /static/ { root /var/www/site/static; }, Nginx looks in /var/www/site/static/static/. Use alias /var/www/site/static/; instead, with the slashes matching on both sides.
Cause 6: Uploaded files with wrong ownership
The site works, but new uploads or files deployed by CI return 403. The upload or deploy process created them with a restrictive umask (mode 600 or 700). Check with ls -l, fix with the find ... chmod commands above, and set the umask in your deploy script or app so new files come out as 644. My GitHub Actions deploy sets permissions as part of the release step so this never comes back.
Checklist after the fix
- Test the config and reload:
sudo nginx -t && sudo systemctl reload nginx. If Nginx won't come back, see Job for nginx.service failed. - Test as the Nginx user with
sudo -u www-data, not as root. Root can read everything, so it proves nothing. - Check the next error. If the 403 becomes a 502, the static part is fixed and the app behind Nginx is down: see Nginx 502 Bad Gateway.
- Keep secrets out of the web root. A 403 on
.envor.gitis correct. Better still, keep them outside the folder Nginx serves.
Frequently asked questions
What causes 403 Forbidden in Nginx?
Nginx refuses requests it can't or won't serve. The usual causes are a folder without an index file, file or folder permissions that stop the www-data user, a deny rule in the config, or SELinux labels on Red Hat systems. The error log names which one.
Should I chmod 777 to fix a 403?
No. It makes every file writable by any process on the server, so one compromised app can change your whole site. Use 755 for folders and 644 for files, owned by your deploy user, and make sure parent folders are passable.
Why does Nginx return 403 instead of 404 for a missing page?
When the URL maps to a folder that exists but has no index file, Nginx returns 403 because listing the folder is forbidden. A URL that maps to nothing at all returns 404.
How do I enable directory listing in Nginx?
Add autoindex on; inside the location block. Only do this for folders meant to be public, like a downloads area, because it shows every file name in the folder.
Why do I get 403 Forbidden from Cloudflare but not from my server?
If the response header says server: cloudflare and your Nginx log is empty, Cloudflare's firewall, bot protection or a WAF rule blocked the visitor. Check Security, then Events, in the Cloudflare dashboard for the blocked request and the rule that matched.
Need your server set up properly?
I set up Nginx, SSL and permissions on Ubuntu servers so deploys don't break them, and fix 403s and other server errors the same day. See my VPS setup with Nginx and free SSL, all Linux system admin services, or contact me with your domain and the error log line.
Written by
MD Rakibul Islam Rakib
Full-stack developer, DevOps engineer and Linux system administrator with 5+ years of production experience. I deploy, harden and fix servers and web apps for clients worldwide, and everything in this article runs on real servers I manage, including this site.
- Nginx 403 Forbidden
- directory index is forbidden
- 13: Permission denied
- www-data permissions
- namei
- SELinux
- Ubuntu


