Nginx Error: 'directory index of '/var/www/html/app/' is forbidden' — Cause, Fix, and Troubleshooting Guide
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
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) returns403 Forbidden;GET /app/page.htmlworks fine.error.logshowsdirectory index of "..." is forbiddenwith no(13)or(2)errno.- The directory exists and is readable by the worker —
lsas 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
autoindexmakes 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(orindex.php, etc.) was never deployed, was renamed, or a build step failed to emit it. - The
indexdirective does not name the file that exists. The directory containshome.htmlormain.php, butindexonly listsindex.html index.htm, so NGINX never matches it. autoindexis 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’sindex.htmlviatry_files, but without it NGINX treats/app/as a bare directory. - Wrong
root/aliasmaps the URL to a directory with no index. A misconfiguredaliascan 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 onis an information-disclosure risk. It reveals filenames, sizes, and timestamps. Restrict it to a single intendedlocationand never enable it globally.indexfiles 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.htmlmasks this error for SPAs — but make sure the fallback file actually exists, or you convert a403into an infinitetry_filesmiss.- Trailing slash matters.
/appmay redirect to/app/first; the directory-index check only happens once the request resolves to a directory. - Check
aliasvsrootsemantics. Withalias, 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 forbiddenin the log so a deploy that dropsindex.htmlis caught immediately, not by a user.
Related identity errors
- Nginx Error: 403 Forbidden — the umbrella
403guide; this directory-index case is one specific trigger of it. - Nginx Error: Open Failed No Such File Static — the related case where the requested file itself is missing (
2: No such file), rather than a directory with no index. - Nginx Error: Permission Denied Connecting Upstream Selinux — a different
403/denied path caused by SELinux on upstream connections, useful to rule out.
See the NGINX category for more guides.
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?
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.