Nginx autoindex: Making Directories Directly Browsable in the Browser
You have a batch of files on a server you want colleagues to browse directly, without writing any pages — autoindex is Nginx’s built-in directory listing: when the requested directory holds no default index file such as index.html, Nginx automatically generates an HTML page listing links to every file and subdirectory. This post, in an nginx:1.28 container, runs through the switch, the two display options, the JSON output, and two classic cases of “why am I not seeing a listing”.
Lab Environment
- Docker container
nginx:1.28-alpine(nginx/1.28.3, tested 2026-09; autoindex is a standard module, compiled in by default). - Three test directories:
files/(holding three files: readme.txt, big-archive.tar, app.log),plain/(an empty directory, nothing configured), andwith-index/(containing both index.html and other.txt).
The Core Mechanism in One Sentence
When Nginx receives a request pointing to a directory, it first looks for a default index file per the index directive; if none is found, autoindex on decides whether it generates a directory listing page (on) or returns 403 Forbidden (off by default). The listing page is generated on the fly by Nginx; it does not exist on disk.
Example One: On vs. Off — the Difference Between 403 and a Listing
First look at a directory with nothing configured — plain/ has no index.html and no autoindex:
1 | location /plain/ { } |
Requesting it returns 403 Forbidden. Add autoindex on and look at files/:
1 | location /files/ { |

Same Nginx: plain/ gives 403 while files/ turns into a linked file listing — the size of big-archive.tar displays as 1M (the friendly-unit effect of autoindex_exact_size off), and the timestamps are the server’s local time (autoindex_localtime on). These two options do not affect functionality, only the readability of the listing: autoindex_exact_size defaults to on, showing exact byte counts; autoindex_localtime defaults to off, showing GMT times.
Example Two: autoindex_format json, a Directory Manifest for Scripts
Browsers want HTML; scripts want structured data. autoindex_format (introduced in 1.7.9) supports four outputs: html | xml | json | jsonp:
1 | location /json/ { |

The same directory, and in the JSON output every file carries four fields — name/type/mtime/size — with size as the exact byte count (unaffected by autoindex_exact_size). Paired with curl, a single command handles small jobs like directory syncs and delta checks:
1 | curl -s http://example.com/json/ | grep -o '"size":[0-9]*' |
Wrong-Way Comparison: Two Reasons a Listing Does Not Appear
Reason one: the directory contains index.html. with-index/ holds both index.html and other.txt; even with autoindex on enabled, requesting it returns the content of index.html — the index file outranks the directory listing:
1 | # Wrong assumption: with autoindex on you will always see a listing |
Reason two: autoindex neither enabled nor any index file present — that is Example One’s 403. To troubleshoot “why no listing”, go in this order: first check whether an index.html in the directory is shadowing it, then whether autoindex on actually took effect on the right location.

Notes
- autoindex exposes a directory as-is; directories holding configuration, backups, or sensitive data must never enable it; for directories that do, tighten access with auth_basic or allow/deny.
- A 403 is not necessarily “no permission” — it can also be a directory that should have autoindex but lacks it; when troubleshooting, first distinguish the two 403 semantics: “access denied” vs. “listing feature missing”.
- The listing page is generated dynamically; a large directory (tens of thousands of files) is walked on every request — for high-traffic large directories consider caching or a pre-generated static index.
- Under the json format, exact_size has no effect; size is always the exact byte count, so in scripts just compare it numerically.
Summary
Back to the opening scenario: without writing a single line of code, make a batch of files browsable — one switch, autoindex on, does it; autoindex_exact_size and autoindex_localtime make the listing friendlier to read; and autoindex_format json even lets scripts consume it directly. Remember the two boundaries: when index.html is present in the directory, the listing is shadowed; a directory with nothing configured gives 403, not an empty listing. Internal document distribution and build-artifact showcases — this feature set fits them perfectly.
This article was rebuilt from the author’s CSDN blog posts written between 2020 and 2024, originally published on CSDN.