Home/Infrastructure/nginx 403 Forbidden Error: 5-Minute Diagnosis and Fix Guide by 6 Root Causes
Infrastructurenginx 403403 Forbidden

nginx 403 Forbidden Error: 5-Minute Diagnosis and Fix Guide by 6 Root Causes

Classify nginx 403 Forbidden into six root causes—permissions, missing index, root/alias, SELinux, deny, and try_files—diagnose from error_log strings, and fix each case on the spot with copy-paste commands like ls, namei, and chcon.

nginx 403 Forbidden Error: 5-Minute Diagnosis and Fix Guide by 6 Root Causes
Contents
  1. "Why is a page that was working a minute ago returning 403?"
  2. Get oriented in 5 seconds: branch on the error_log string
  3. Classification table: the 6 causes of 403
  4. Practical fixes by scenario
  5. A field note: these days, containers are the real trap
  6. 5-minute diagnostic checklist
  7. References: official docs
  8. Frequently asked questions (FAQ)

nginx 403 Forbidden: Diagnose and Fix the 6 Root Causes in 5 Minutes

"Why is a page that was working a minute ago returning 403?"

You just finished a deploy and the browser shows 403 Forbidden. A lot of people reflexively blast chmod 777 at this point. If you're lucky it unblocks, but you've only left a security hole—and the next time 403 shows up, you'll wander the same maze.

The key point first: nginx 403 is not just a permissions problem. There are at least six causes, and each leaves a different string in error_log. The right approach is not guessing at permissions—it's pinpointing the cause from a single log line.

Don't panic. Look at the logs first.

Bash
# run — check
sudo tail -n 30 /var/log/nginx/error.log

Get oriented in 5 seconds: branch on the error_log string

When you open the log, you'll usually see one of the three strings below. That's your diagnostic branch point.

Representative error_log stringLikely cause
directory index of "/var/www/html/" is forbidden② Missing index file (autoindex off)
access forbidden by rule⑤ deny rule
Permission denied (errno 13)① File/directory permissions or ④ SELinux

Real log samples look like this.

LOG
2026/06/14 10:21:33 [error] 812#812: *5 directory index of "/var/www/html/" is forbidden, client: 10.0.0.5 ...
2026/06/14 10:24:01 [error] 812#812: *7 access forbidden by rule, client: 10.0.0.5 ...
2026/06/14 10:27:18 [error] 812#812: *9 open() "/var/www/html/index.html" failed (13: Permission denied) ...

Seeing Permission denied but permissions look fine? Then it's almost certainly SELinux. (Especially common on RHEL/CentOS/Rocky families.)

Classification table: the 6 causes of 403

#Causeerror_log stringOne-line diagnostic commandCore fix
①File/directory permissionsPermission denied (13)namei -l /var/www/html/index.htmlDirectories 755, files 644, matching owner
②Missing indexdirectory index ... is forbiddenls -la /var/www/htmlPlace an index file or autoindex on
③Misconfigured root/alias pathopen() ... No such file or 403nginx -T | grep -E 'root|alias'Check the path; fix alias trailing slashes
④SELinux contextPermission denied (13)getenforce + ausearch -m avcchcon -t httpd_sys_content_t
⑤deny ruleaccess forbidden by rulenginx -T | grep -E 'deny|allow'Revisit allow/deny order and target IPs
⑥try_files / location conflict(403 with no explicit error)Review location blocks with nginx -TSort out try_files and location precedence

Copy-paste diagnostic and fix commands

Bash
ls -la /var/www/html # file/ · check
namei -l /var/www/html/index.html # path
sudo chown -R www-data:www-data /var/www/html # Debian/Ubuntu (RHEL nginx:nginx)
sudo chmod -R 755 /var/www/html # (file after 644 )
getenforce # SELinux check (Enforcing )
sudo chcon -R -t httpd_sys_content_t /var/www/html # nginx
sudo ausearch -m avc -ts recent # SELinux (AVC) check
nginx -t && systemctl reload nginx # settings after

⚠️ Owner caveat: Debian/Ubuntu uses www-data; RHEL/CentOS/Rocky uses nginx as the default worker user. Confirm the actual worker user with ps aux | grep nginx.

Practical fixes by scenario

Even for the same 403, the likely causes differ by how you serve content.

Static sites — ② and ⑥ are common

Nginx
server {
    root /var/www/html;
    index index.html;
    location / {
 try_files $uri $uri/ =404; # index when 403 404
    }
}

If try_files includes $uri/, nginx tries directory auto-lookup and returns 403 when there's no index. Explicitly using =404 makes the intent clear.

Reverse proxy — missing location match

Nginx
location /api/ {
    proxy_pass http://127.0.0.1:8080/;
}
# location / root 403/404

If you get a 403 on a reverse proxy, it may be a 403 returned by the backend (upstream), not nginx. If error_log has no nginx-specific string, suspect the upstream response.

PHP-FPM — try_files + fastcgi pattern

Nginx
location ~ \.php$ {
    try_files $uri =404;
    include fastcgi_params;
    fastcgi_pass unix:/run/php/php-fpm.sock;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}

If you omit try_files $uri =404, a 403 can occur for PHP paths that don't exist.

A field note: these days, containers are the real trap

The #1 cause of the recent surge in 403 tickets in the field is, surprisingly, UID/GID mismatch on Docker/k8s volume mounts. Host files owned by 1000:1000 can't be read by nginx (UID 101) inside the container, so you get Permission denied. In that case, check the worker UID with id inside the container before you chmod on the host. And on RHEL-family systems, setenforce 0 because "just turn off SELinux" is only a stopgap. Fixing the context is the right answer, and if you codify permissions and context with Ansible, recurrence almost disappears.

5-minute diagnostic checklist

  1. Check the last 30 lines of error_log → branch on the string
  2. index/directory string → autoindex or place an index
  3. access forbidden by rule → inspect deny/allow order
  4. Permission denied → trace path permissions with namei -l
  5. Denied even though permissions look fine → check SELinux with getenforce + ausearch
  6. Finish with nginx -t && systemctl reload nginx

Related reading: if uploads are blocked, see 413 Request Entity Too Large; if the backend connection drops, see the 502/504 Gateway troubleshooting guide.

References: official docs

The primary source for the behavior, settings, and errors covered in this post is the official documentation below. Check it for version-specific options and exact behavior.

Frequently asked questions (FAQ)

Q. It was fixed with chmod 777 — why shouldn't I use that? A. Anyone can read, write, and execute, so the risk of webshell upload and tampering is high. The standard is 755 for directories and 644 for files; grant write only on directories that actually need it.

Q. File permissions are all correct, but I still get 403. A. It's often blocked at a parent directory (e.g. a home directory with 700). Tracing the full path with namei -l /home/user/site/index.html shows in one line which hop is blocking. For the nginx worker to reach the end of the path, every parent directory needs execute (x) permission.

Q. Can't I just turn SELinux off? A. setenforce 0 drops an entire security layer. On RHEL-family systems the recommended path is to stay Enforcing: fix only the context with chcon -t httpd_sys_content_t, and make it persistent with semanage fcontext.

Q. Why does a trailing slash on alias flip between 403 and 404? A. alias substitutes the location path, so slash alignment matters. Match slashes on both sides, as in location /img/ { alias /data/img/; }. If only one side has the slash, the path goes wrong and you get 403/404.

Q. I'm on a reverse proxy and getting 403 — is it an nginx problem? A. Not necessarily. nginx may have proxy_passed correctly and the backend (upstream) returned 403. If error_log has no nginx-specific string, check the backend application logs.

✦ ✦ ✦
Sources and verification

Sources and checks vary by article. Only articles with a recorded check date, scope and sources below were checked, and only within that scope. For other articles, verify against the current docs for your version before applying anything, and email us about errors. Editorial policy · email us

This article has no recorded human check (date and scope) yet.

Published ·
Topic-related official docs (auto-linked · not per-claim sources)NGINX documentation ↗Linux man pages ↗

Comments

Be the first to comment.