# WordPress uploads folder security: stop PHP and folder listings

How to secure the WordPress uploads folder: stop PHP files there from running, stop folders listing their files, what each can break, and how to check.

By M Asif Rahman, Oct 14, 2026. Updated Nov 2, 2026.

WordPress uploads folder security comes down to two questions: can a file someone uploaded run as code, and can anyone see a list of everything in the folder? On a default install the answer to both can be yes. This guide covers the two BetterShield fixes that close them, **Uploads code block** and **Folder listing protection**, what each one changes, what it can break, and how to check it worked.

The uploads folder, wp-content/uploads on most sites, holds every image, PDF and file your site was given. It is also the one folder WordPress itself writes to all day, which is why it deserves its own rules. [WordPress hardening](/blog/wordpress-hardening/) covers all thirty fixes; this post goes deeper on these two.

## Quick summary

- **Uploads code block** writes one marked block into the uploads .htaccess, so a request for a PHP file there answers 404 instead of running. It needs a server that reads that kind of rule: Apache, or LiteSpeed's paid edition.
- **Folder listing protection** places a blank index.php in folders that have no index file, two levels deep. It works on every web server.
- Both are under **BetterShield › Protection › Hardening**, both start off, both show **Preview the change** first, and both have an undo that never expires.
- Images and documents keep loading at their own addresses. Only code run from uploads, and folder lists, stop.

## Why the uploads folder needs its own rules

Two things make uploads different from the rest of your site.

First, the files in it came from people, not from your developer. A contact form, a profile picture, a course submission, a product image: each is a file the site accepted. If a file ending in .php ever lands there, through a plugin that checks file types too loosely or an account allowed to upload more than it should, the web server will happily run it when someone asks for its address. In the plugin's words, that is the most common way an uploaded file becomes a way in.

Second, WordPress ships blank index files in wp-content, plugins and themes, but leaves the uploads folder without one. On a server that lists folder contents, anyone who opens /wp-content/uploads/2026/10/ can read the name of every file uploaded that month, including the ones you uploaded but never linked.

Neither fix scans files or removes anything. They change what the server does when a file or a folder is asked for. Backups, logs and wp-config copies sit outside uploads and have their own fix: see [how to hide sensitive files in WordPress](/blog/hide-sensitive-files-wordpress/).

## Block code in the uploads folder

The fix writes this block into the .htaccess file in your uploads folder, creating the file if there is none, and touches nothing else in it:

```apache
# BEGIN BetterShield uploads-php-execution
RedirectMatch 404 "(?i)\.(php|phar|pht|phtm|phtml|phps|php[0-9])(\.|/|$)"
# END BetterShield uploads-php-execution
```

Any request under uploads for a name ending in .php, .phar, .pht, .phtm, .phtml, .phps, or .php and a digit (such as .php7) is answered 404 before it reaches PHP, in any mix of capitals, and also with something added after the name, such as image.php.jpg or file.php/extra. Every other file is served as before.

Your web server applies the rule, not WordPress, so it stays in force during safe mode and while BetterShield is deactivated.

### Which servers it works on

| Server | On the row |
|---|---|
| Apache | Available. |
| LiteSpeed | Available. The note says LiteSpeed's paid edition reads the rule and the free OpenLiteSpeed skips it, so BetterShield could not confirm it is enforced. |
| OpenLiteSpeed | **Not available here**: it reads only rewrite rules from .htaccess files. |
| nginx, IIS | **Not available here**: the server does not read .htaccess files. |

On nginx, IIS or OpenLiteSpeed, ask your host to refuse PHP files under uploads in the server configuration. A fix that cannot work on your server cannot be switched on, and the row says why, so you never get a switch that reads on and does nothing.

### Set it up

1. Open **BetterShield › Protection › Hardening** and find **Uploads code block** under **What can run**.
2. Read any note under the description, about your server or about rules another plugin already keeps in the uploads .htaccess.
3. Press **Preview the change**. Nothing is written.
4. Turn the switch on. The row shows **On since** and **Undo never expires**.
5. Open an image from your Media Library in a new tab, to confirm uploads still load.

From the terminal, `wp bettershield harden uploads_php --dry-run` previews it and `wp bettershield harden uploads_php --user=<login>` applies it. An AI assistant can neither apply nor remove this fix: it writes to a file, so it stays your switch.

## Hide folder contents

This fix places a blank index.php in the uploads folder and the folders below it, two levels deep. On the usual layout that is uploads itself, each year folder and each month folder. A folder that already has an index.php or index.html is left alone, and no file is ever overwritten. Quick Setup offers the same fix as **Block directory browsing**.

It needs no server rules, so it works on nginx and IIS too. On a large uploads folder, one pass places up to 500 files and a daily task places the rest, 500 a day. After that, the daily task covers the current month's folder each day and walks the whole tree again each week, so new folders are covered as they appear.

### Set it up

1. On **BetterShield › Protection › Hardening**, find **Folder listing protection** under **What the site reveals**.
2. Press **Preview the change**.
3. Turn the switch on. The row says what it did, for example "Guard files were placed in 14 uploads directories that had none."

From WP-CLI, `wp bettershield harden directory_browsing --user=<login>` applies it.

## What each fix can break, and how to undo

**Uploads code block** can stop code that was meant to run from uploads: anything that opens a PHP file there by its web address now gets a 404. Code that loads such a file from inside PHP is not affected. A download named with .php followed by a dot, such as notes.php.txt, also answers 404; rename it. On a server that refuses this kind of rule in .htaccess, files in uploads can fail with a server error while the dashboard and sign-in keep working. That is what step 5 above checks for: if the image fails, turn the switch off and ask your host.

**Folder listing protection** breaks very little. If you shared a folder listing from uploads on purpose, it stops; link to the files themselves instead.

To undo either one, turn its switch off. BetterShield removes exactly what it added: its block in .htaccess (and the file itself if it created it and nothing else is in it), or the index files it placed, but only those still exactly as it wrote them. If **Ask for my password before an action that removes a protection** is on under **Settings › General**, it asks for your password first.

## Check it is working

- **PHP in uploads.** Over SFTP or your host's file manager, put an empty file named test.php in your uploads folder and open yoursite.com/wp-content/uploads/test.php. A "not found" page means the rule works; a blank page means PHP ran the file. Delete the test file afterwards.
- **Folder listings.** In a private window, open /wp-content/uploads/ and a month folder such as /wp-content/uploads/2026/10/. You no longer get a list of files.
- **The activity log.** "Hardening applied" for each fix, with who applied it and when. See [Activity log](/docs/activity-log/).
- **Findings.** While either fix is on, **Some protections that are switched on are not working** (Medium) names it if its block or its files go missing.

## Recommended settings

We recommend both on every site whose server supports them. Uploads hold files people gave the site, and none of them need to run as code or be listed. BetterShield's file check never looks inside uploads, so these two rules are what cover that folder.

If the audit reports **Non-admin users can upload files that can run code** and its note names a PHP type, turn on **Uploads code block**, and also change the plugin or theme setting that allows that type. The fix stops the file running; it does not stop it being uploaded.

## Frequently asked questions

### Will stopping PHP in uploads break my images or PDFs?

No. The rule only matches names that end in a PHP extension, or carry one before another dot or slash. Images, documents and every other file keep loading at their own addresses.

### My site runs on nginx. What can I do?

**Uploads code block** shows **Not available here**, because nginx does not read .htaccess files. Ask your host to refuse PHP files under the uploads folder in the server configuration. **Folder listing protection** works on nginx as it is.

### Does this scan my uploads for malware?

No. BetterShield does not scan for or remove malware. These fixes change what the server does when a file or a folder is requested.

### Does it work on multisite?

Yes. Applied from the main site, **Uploads code block** covers every site, because each site's uploads folder sits inside the main site's. **Folder listing protection** is applied on each site. See [Multisite networks](/docs/multisite-network/).

## Conclusion

Two switches close the uploads folder: one stops anything there running as code, the other stops anyone reading a list of what is in it. Both preview first, both leave your images alone, and both come off with one click.

BetterShield is free on [WordPress.org](https://wordpress.org/plugins/bettershield/). The docs cover each fix in full: [Stop PHP in uploads](/docs/php-in-uploads/) and [Stop uploads listings](/docs/uploads-directory-listing/).
