Skip to content
🎉 Launch sale:50% off everything over $22 — automatically applied at checkout· ends Aug 2Shop the sale →
DevOps AI ToolKit
Newsletter
All guides
AI for NGINX By James Joyner IV · · 8 min read Last reviewed Jul 2026

Nginx Error: 'directory index of '/var/www/html/app/' is forbidden' — Cause, Fix, and Troubleshooting Guide

Quick answer

Fix directory index of /var/www/html/app/ is forbidden (403): add an index file, set the index directive, use try_files, or enable autoindex.

  • #nginx
  • #web-server
  • #troubleshooting
Free toolkit

Stuck on this NGINX error? Get the free incident triage checklist

A one-page PDF — the exact steps to isolate, fix, and verify a production error like this one. No spam, unsubscribe anytime.

What this error means

This error means a client requested a directory (a URL ending in /), NGINX looked inside it for an index file listed by the index directive, found none, and — because autoindex is off (the default) — refused to generate a directory listing. Rather than expose the folder contents, NGINX returns 403 Forbidden and logs:

2026/07/12 11:42:08 [error] 3120#3120: *8842 directory index of "/var/www/html/app/" is forbidden, client: 203.0.113.9, server: example.com, request: "GET /app/ HTTP/1.1", host: "example.com"

Note the errno-free wording: this is not a permissions problem (13) or a missing-path problem (2). The directory exists and is readable; there is simply no index file to serve and directory listing is disabled. Impact is a hard 403 on every request that resolves to a directory without an index — often a whole section of a site after a deploy that dropped or renamed index.html.

Where it surfaces

  • GET /app/ (trailing slash, a directory) returns 403 Forbidden; GET /app/page.html works fine.
  • error.log shows directory index of "..." is forbidden with no (13) or (2) errno.
  • The directory exists and is readable by the worker — ls as the NGINX user succeeds.
  • It started after a deploy that removed, renamed, or failed to build index.html.
  • The site root / itself may 403 if the document root has no index file.
  • Turning on autoindex makes the request succeed (with a file listing), confirming the cause.

Identity and permission causes

  • No index file present in the directory. The most common case: index.html (or index.php, etc.) was never deployed, was renamed, or a build step failed to emit it.
  • The index directive does not name the file that exists. The directory contains home.html or main.php, but index only lists index.html index.htm, so NGINX never matches it.
  • autoindex is off (the default) and a listing was actually expected. For a downloads or assets folder you may genuinely want a browsable listing, which is disabled until you opt in.
  • A single-page app expects a fallback that isn’t configured. SPA routes like /app/ should rewrite to the app’s index.html via try_files, but without it NGINX treats /app/ as a bare directory.
  • Wrong root/alias maps the URL to a directory with no index. A misconfigured alias can point /app/ at a folder that was never meant to be served directly.

Unlike open() ... (13: Permission denied), there is no filesystem permission to fix here — NGINX successfully read the directory. The resolution is always about what to serve for a directory URL, not about chmod.

Tracing the failed authorization

Confirm the config is valid and see exactly which index, root, and autoindex settings apply:

sudo nginx -t
sudo nginx -T | grep -E "index|autoindex|root|alias|try_files"

Resolve the URL to a real filesystem path and check what is (and isn’t) in the directory:

# List the directory NGINX complained about
ls -la /var/www/html/app/

# Is there any file matching the index directive?
ls -la /var/www/html/app/ | grep -E "index\.(html|htm|php)"

Reproduce the exact request and read the status and the log:

curl -I http://example.com/app/
sudo tail -n 20 /var/log/nginx/error.log | grep "directory index"

If ls shows the directory exists and is readable but contains no index.* file, you have confirmed the cause: NGINX has nothing to serve for the directory and listing is off.

Resolution

Pick the fix that matches intent. Do not reach for autoindex on unless a public listing is genuinely what you want.

1. Provide an index file (most common, correct for normal sites). Deploy the missing file, or fix the build/deploy step that was supposed to create it:

ls /var/www/html/app/index.html || echo "index.html is missing — fix the deploy"

2. Point the index directive at the file that exists. If the directory’s landing page is not called index.html, list it:

server {
    listen 80;
    server_name example.com;
    root /var/www/html;

    # Try each name in order; first one that exists is served
    index index.html index.htm home.html;
}

3. Use try_files for single-page apps. Route directory (and unknown) URLs to the app’s entry point instead of treating them as bare directories:

location /app/ {
    root /var/www/html;
    try_files $uri $uri/ /app/index.html;
}

4. Enable autoindex only when a browsable listing is intended. Appropriate for a downloads or artifact directory — never for a directory containing source, config, or private files:

location /downloads/ {
    root /var/www/html;
    autoindex on;              # generates an HTML listing
    autoindex_exact_size off;  # human-readable sizes
    autoindex_localtime on;    # local timezone in the listing
}

Be deliberate: autoindex on exposes every filename in the directory to the public, so scope it to a specific location, never the whole root.

After any change, validate and reload:

sudo nginx -t && sudo systemctl reload nginx

Hardening access

  • autoindex on is an information-disclosure risk. It reveals filenames, sizes, and timestamps. Restrict it to a single intended location and never enable it globally.
  • index files are tried in order; the first that exists wins. Order matters if a directory can contain more than one candidate.
  • try_files $uri $uri/ /index.html masks this error for SPAs — but make sure the fallback file actually exists, or you convert a 403 into an infinite try_files miss.
  • Trailing slash matters. /app may redirect to /app/ first; the directory-index check only happens once the request resolves to a directory.
  • Check alias vs root semantics. With alias, the URL path is replaced, not appended — a subtle misconfiguration can map a location to the wrong directory that lacks an index.
  • Alert on directory index of ... is forbidden in the log so a deploy that drops index.html is caught immediately, not by a user.

See the NGINX category for more guides.

Free download · 368-page PDF

Fixed it? Get 500 NGINX & DevOps AI prompts — free

500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.

  • 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
  • Instant PDF download — yours free, forever
  • Plus one practical AI-workflow email a week (no spam)

Single opt-in · unsubscribe anytime · no spam.

Did this fix your issue?

Free download · 368-page PDF

Get 500 Battle-Tested DevOps AI Prompts — Free

500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.

  • 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
  • Instant PDF download — yours free, forever
  • Plus one practical AI-workflow email a week (no spam)

Single opt-in · unsubscribe anytime · no spam.