Ahosting Logo
Knowledge Base

Directory Indexes and Hidden Files in cPanel

An index file removes the list, not the filesWhat an empty index.html does· stops the directory listing· removes the convenient index of what is thereWhat it does not do· the files are still there· still downloadable by name· a guessed or leaked filename still worksWhat actually keeps a file privatePut it outside public_html. Nothing that must stay private belongs in a served directory,whatever the listing setting says.

When a visitor requests a directory with no index file, the server has two options: list the contents, or refuse. Which one it does is a setting, and the default is frequently the wrong one for directories nobody meant to be browsable.

What a directory listing exposes

Everything in that directory, by name, downloadable.

On an uploads folder that is usually harmless. On a directory containing a database export, an old backup archive, a configuration file or a set of documents that were meant for specific people, it is a straightforward data exposure, and one that requires no skill to find, because search engines index directory listings.

The files were always downloadable by anyone who guessed the name. A listing removes the guessing.

Turning it off

In cPanel, open Indexes under Advanced. Choose a directory and set it to No Indexing.

Set it on the top level and it applies beneath, which is the sensible default: turn indexing off for the account and enable it deliberately where you actually want a browsable list.

The setting is written into .htaccess as:

Options -Indexes

You can add that line by hand, which is useful when you want it in a directory the panel does not conveniently reach.

An index file is not protection

The common workaround is dropping an empty index.html into a directory so the server serves that instead of a listing.

It works, and it is not protection. The files are still there and still downloadable by name; you have only removed the convenient list. Anyone who knows or guesses a filename still gets the file.

For files that genuinely should not be public, the answer is to move them above public_html, where nothing is served, or to password-protect the directory. Password protecting directories goes into it.

Hidden files are not hidden either

A filename beginning with a dot is hidden from a file listing. It is not hidden from the web.

Two consequences worth knowing. A .git directory inside public_html exposes your entire source history including anything ever committed to it. And backup files that editors leave behind. A wp-config.php.bak or a .save file are served as plain text rather than executed, which means the database password in them is readable in a browser.

That second one is a real and common exposure. A file named wp-config.php runs as PHP and outputs nothing; the same file named wp-config.php.bak is downloaded as text.

What should never be in public_html

Database exports. Backup archives. Configuration files with credentials in them. Editor backup copies. Repository directories. Anything you were told to place there temporarily and did not remove.

Intermediate files belong above the web root, where nothing is served regardless of what any index setting says: that is the only arrangement that does not depend on getting a configuration right.

Where a listing is genuinely useful

A directory of downloads you intend people to browse. A file share for a group, behind directory protection.

Those are legitimate, and the setting exists for them. The point is to enable it deliberately in those places instead of leaving it on everywhere by default.

Check what is currently exposed

Worth five minutes on any site you inherited.

Load a few directory paths in a browser, your uploads directory, any folder you use for temporary files, the site root's subdirectories. If you get a list, that directory is browsable by anyone.

Then search for your own domain with a search engine's site query and look for anything that reads like a file listing or a stray archive. Indexed exposures are the ones already being found by other people.

Find what is exposed before deciding anything

Rather than reasoning about it, look at what the server currently serves.

for p in .git/config .env wp-config.php.bak backup.sql composer.json; do
 printf '%-24s %s\n' "$p" "$(curl -s -o /dev/null -w '%{http_code}' "https://example.com/$p")"
done

Anything returning 200 is being served to anyone who asks. The results are frequently surprising on a site that has been through several developers.

A .git directory is the worst of these: it contains the entire history of the code, including credentials that were committed and later removed, removed from the current files and still present in the history.

Deny by pattern rather than by name

Blocking files individually means blocking the ones you thought of. A pattern covers the ones you did not.

<FilesMatch "(^\.|\.(bak|old|orig|save|swp|sql|log|env|ini)$)">
 Require all denied
</FilesMatch>
RedirectMatch 404 /\.git(/|$)

That refuses anything beginning with a dot and anything with an extension that is characteristically not meant to be public.

Test each pattern after adding it. A rule that looks right and does nothing is common, and the only way to know is to request the file and see a refusal.

Editors leave files behind

The files that end up exposed are rarely put there deliberately.

Editing a file over SFTP leaves a swap file. A panel's editor leaves a backup copy. A hurried change produces config.php.old. None of these is recognised by the server as code, so all of them are served as text.

That is why editing live files is a habit worth avoiding beyond the risk of breaking the site, and why the patterns above matter more than remembering to tidy up. Making a development copy of a site goes into the alternative.

Listings on subdomains and addon domains

The setting is per directory, and each addon domain and subdomain has its own root.

A main site with listings correctly disabled and a staging subdomain with them on is the usual state, and the staging copy is the one with more interesting things in it: old databases, archives, a copy of the site from before a redesign.

Check every hostname the account serves instead of the main one, which is the same enumeration needed for certificates. For listing them, see SSL for subdomains and addon domains.