Nginx 403 Forbidden — Permissions, Index, or Deliberate Deny

Nginx refused to serve — and it's telling you why in error.log: file permissions, missing index directive, or a deny rule. Four layers to check, in order.

What you'll see

Root causes

Filesystem permissions: nginx user can't read the path

Every component of the path needs +x for the nginx user, the file needs +r. namei -l /var/www/site/index.html walks the whole chain showing exactly which link fails.

No index directive and autoindex off

Requesting / when index.html isn't present and autoindex is off → 403 (not 404, by design). Fix: index index.html; or create the file.

Deliberate deny/allow rules or SELinux

deny directives, geo rules, or SELinux denials (CentOS/RHEL: audit.log shows avc denied). Test by temporarily checking error.log wording: 'client denied' = nginx rule; 'Permission denied' = filesystem/SELinux.

Fix it

  1. Let error.log name the layer
    sudo tail -5 /var/log/nginx/error.log   # 'client denied' = config rule; 'Permission denied' = filesystem/SELinux
  2. Walk the permission chain
    namei -l /var/www/site/index.html   # every component needs x for www-data, file needs r
  3. Fix perms deliberately (not chmod -R 777)
    sudo chown -R www-data:www-data /var/www/site && sudo find /var/www/site -type d -exec chmod 755 {} \; && find /var/www/site -type f -exec chmod 644 {} \;
  4. Check index/autoindex for directory URLs
    # location / { index index.html; }  — or autoindex on; only where listing is intended

Field note

chmod -R 777 'fixes' it while creating a far worse problem: any process can rewrite your webroot. It also hides the real missing-permission link. SELinux contexts (restorecon -Rv) trip RHEL-family servers where everything looks permission-correct.

Common questions

Why 403 instead of 404 for a missing index file?

When the directory exists but no index file matches (and listing is off), nginx refuses rather than reveals the directory structure. It's by design — create the index file or set the index directive.

How do I know if it's nginx config vs. filesystem?

The error log wording decides: 'client denied by server configuration' = a rule you wrote; 'Permission denied' / open() errors = filesystem or SELinux. Same 403, different owners.

Ship it right the first time

Our most-documented failures, packaged as ready-to-ship starter kits: Docker, Kubernetes, and Terraform.

Browse the template store →

One-time. Yours to modify. Instant download from the NinjaOps template store.