How to Disable Directory Listing in Nginx

How to Disable Directory Listing in Nginx

A few years ago I was doing a routine security check on a client’s server and stumbled onto something that made my stomach drop: navigating to /uploads/ on their site showed a full, browsable file listing — every filename, every file size, every upload timestamp, all sitting there in plain view for anyone who typed the right URL. Nothing had been “hacked.” Nginx was just doing exactly what it does by default when it can’t find an index file in a directory: it shows you what’s inside. This guide is about making sure that never happens to you.

What Is Directory Listing, and Why Does It Happen?

When a client requests a URL that maps to a directory rather than a specific file — say, https://example.com/uploads/ — a web server has to decide what to do. If there’s an index file (like index.html) in that directory, it serves that. If there isn’t, and directory listing (sometimes called “directory indexing” or “autoindex”) is enabled, the server generates an HTML page listing every file and subdirectory it finds there.

Nginx’s directory listing feature is controlled by the autoindex directive, and it’s off by default in a stock Nginx install. So how do sites end up with it exposed? In my experience, it’s almost always one of these:

  • Someone explicitly turned it on temporarily for debugging and forgot to turn it off
  • A copied config from a tutorial or Stack Overflow answer included autoindex on; without the person realizing what it did
  • A directory that’s meant to hold static assets (uploads, backups, logs) simply has no index file, and a previous admin turned on autoindex “so it doesn’t just show a 403”

Why This Matters

Directory listing isn’t a vulnerability in the traditional sense — Nginx isn’t misbehaving, it’s doing exactly what it’s configured to do. But it’s an information disclosure risk that can snowball into something worse:

  • Exposing file structure helps an attacker understand your application’s layout, which can inform further attacks
  • Exposing uploaded files that were meant to be reached only via a direct, unguessable link (a common pattern for “unlisted” file sharing)
  • Exposing backup files.sql.gz, .zip, config.bak — that someone left in a web-accessible directory, which is a classic and still very common way sites get breached
  • Exposing source code or config files if directory listing is accidentally enabled somewhere near your application root

Step 1: Check If Directory Listing Is Currently Enabled

Before making changes, confirm what you’re dealing with. Visit a directory URL on your site directly (e.g., https://example.com/uploads/) in a browser. If you see a plain HTML page with a list of files and folders (usually with an “Index of /uploads” heading), directory listing is on.

You can also check your Nginx config directly:

sudo grep -r "autoindex" /etc/nginx/

This searches every config file under /etc/nginx/ for any autoindex directive, so you can see exactly where it’s enabled.

Step 2: Disable Directory Listing

The fix is almost always this simple. Find the location block (or server block) where autoindex is set, and either remove the line or explicitly set it to off:

location /uploads/ {
    autoindex off;
}

Since autoindex off; is Nginx’s default behavior, you don’t strictly need this line at all unless it’s overriding an autoindex on; set at a broader scope (like the server or http block) — in that case, explicitly setting it to off at the more specific location level is the correct way to override it.

Step 3: Provide a Proper Response Instead

Simply turning off autoindex means Nginx will now return a 403 Forbidden for directory requests that have no index file. That’s usually fine, but I like to be a bit more deliberate about it. Here are a few approaches I use depending on the situation:

Option A: Return a clean 403 with a custom error page

location /uploads/ {
    autoindex off;
}

error_page 403 /403.html;
location = /403.html {
    root /var/www/errors;
    internal;
}

Option B: Redirect directory requests to your homepage

If a bare directory URL genuinely shouldn’t be reachable at all, I sometimes just redirect it:

location /uploads/ {
    autoindex off;
    try_files $uri $uri/ =404;
}

This serves individual files that are directly requested by name (e.g., /uploads/photo123.jpg) but returns a plain 404 for the bare directory path itself, which reveals nothing about the directory’s contents.

Option C: Block the directory entirely if it should never be public

For directories that store things like backups, logs, or internal config — things that should never be reachable over HTTP at all — I go further and deny access outright:

location /backups/ {
    deny all;
    return 404;
}

Returning 404 instead of 403 here is a deliberate choice — a 403 confirms to an attacker that something exists at that path and is just forbidden, while a 404 gives away nothing.

Step 4: Check Every Level of Your Config

This is the step people skip, and it’s the one that actually matters most. autoindex on; set inside the http block in /etc/nginx/nginx.conf applies to every single server and location on that Nginx instance unless something more specific overrides it. I always check:

sudo cat /etc/nginx/nginx.conf | grep -A2 -B2 autoindex

and every included config file:

sudo find /etc/nginx -name "*.conf" -exec grep -l "autoindex" {} \;

If you find autoindex on; at the http level, I’d strongly recommend removing it there and only enabling it explicitly, narrowly, in specific location blocks where you actually want it (for example, an internal-only file archive behind authentication).

Step 5: Test and Reload

sudo nginx -t
sudo systemctl reload nginx

Then re-check the directory URL in a browser or with curl:

curl -I https://example.com/uploads/

You should now see a 403 or 404 status code instead of a 200 with an HTML file listing.

A Note on Legitimate Uses of Directory Listing

I don’t want to make it sound like autoindex on; is always wrong — there are legitimate cases, like an internal file server, a package mirror, or a public downloads page for open-source release artifacts. If you do want directory listing enabled somewhere, I’d combine it with:

  • Basic authentication, if the directory shouldn’t be fully public:
location /internal-files/ {
    autoindex on;
    auth_basic "Restricted";
    auth_basic_user_file /etc/nginx/.htpasswd;
}
  • IP allowlisting, if only specific networks should access it:
location /internal-files/ {
    autoindex on;
    allow 203.0.113.0/24;
    deny all;
}
  • A dedicated, isolated subdomain, so a misconfiguration here doesn’t expose anything from your main application

Testing Your Setup

  1. sudo nginx -t to validate syntax
  2. Visit every directory-style URL on your site you can think of — /uploads/, /images/, /assets/, /backups/, /logs/ — and confirm none of them return a file listing
  3. Run a quick automated check with a tool like nikto or dirb against a staging copy of your site to catch anything you missed manually
  4. Check that legitimate files within those directories (ones meant to be directly linked) still load correctly — you’re disabling the listing, not necessarily access to individual files

Troubleshooting Common Issues

Still seeing a directory listing after adding autoindex off; — You likely have a more specific location block matching the same path with autoindex on; still set, and Nginx’s location matching rules mean the more specific block wins. Search your whole config tree, not just the file you edited.

Getting a 403 when you actually wanted an index page served — Make sure you have an actual index.html (or your app’s default document) in that directory, and that index is set correctly in the relevant location or server block.

Custom error page not showing — Confirm the root path in your error_page location block is correct and that the file actually exists there, and that you’ve included internal; so the error page itself isn’t directly browsable.

Security Considerations

  • Treat autoindex on; as something you enable narrowly and deliberately, never as a default
  • Pair any legitimate use of directory listing with authentication or IP restriction
  • Regularly audit your Nginx config for stray autoindex on; directives, especially after onboarding new team members who might copy-paste configs from tutorials
  • Remember that disabling directory listing doesn’t make files in that directory secure if they’re still directly linkable — it only stops someone from browsing to discover files they didn’t already know the name of
  • Use tools like nikto, dirb, or a simple periodic curl script in CI to catch accidental exposure before an attacker does

Performance Tips

Directory listing itself isn’t really a performance concern, but a couple of related notes:

  • Returning early 403/404 responses for blocked directories is essentially free — Nginx handles this at the config level before touching any backend
  • If you’re serving a large legitimate file listing (like a package mirror), consider autoindex_exact_size off; and autoindex_localtime on; for more human-readable output, and consider caching the generated listing page if it changes infrequently

Real-World Use Case

Going back to that client I mentioned at the start — the fix took about five minutes once we found it: one stray autoindex on; in an old config block for their /uploads/ directory, left over from when someone debugged a file upload issue two years earlier. We turned it off, added try_files $uri $uri/ =404; so directly-linked files still worked, and audited the rest of their config for anything similar. The scariest part wasn’t the fix — it was realizing that misconfiguration had probably been live for two years before anyone noticed.

Best Practices Recap

  • autoindex off; should be your default assumption everywhere
  • Search your entire Nginx config tree for stray autoindex on; directives, not just the file you think is relevant
  • Return 404 rather than 403 for directories that shouldn’t reveal their existence at all
  • If you need directory listing for a legitimate purpose, pair it with authentication or IP restriction
  • Periodically audit your live site for exposed directory listings — it costs nothing and catches real problems

Directory listing exposure is one of those issues that’s completely silent until someone finds it — there’s no error, no crash, nothing in your logs that screams “this is a problem.” It just sits there quietly disclosing information until either you catch it in an audit, or someone else finds it first. A five-minute config check is cheap insurance against that.

Total
1
Shares

Leave a Reply

Previous Post
How to Redirect URLs in Nginx

How to Redirect URLs in Nginx

Next Post
How to Configure Nginx for Django

How to Configure Nginx for Django

Related Posts