
How to block PHP execution in WordPress upload folders?
- Sajjad
- WordPress, Security
- 11 Sep, 2026
To block PHP execution in the WordPress uploads folder, place an .htaccess file inside wp-content/uploads/ that denies access to files ending in .php if your server runs Apache or LiteSpeed, or add a location block to your Nginx configuration that denies PHP requests under /wp-content/uploads/. Security plugins such as Solid Security and Sucuri Security can also apply this hardening for you with a single setting.
The uploads folder is meant to hold images, PDFs, and other media, never executable code. Yet it is writable by WordPress, which makes it one of the first places attackers try to drop a malicious PHP file. This article explains why blocking PHP there matters, walks through the Apache, Nginx, and plugin methods step by step, and shows you how to test the result and clean up anything suspicious you find.
Why Block PHP Execution in the Uploads Folder?
WordPress needs write access to wp-content/uploads/ so it can save the media you upload and generate thumbnail sizes. That same write access is what makes the folder attractive to attackers.
If an attacker finds a way to write a file to your server, for example through a vulnerable plugin with an insecure file upload feature, the uploads folder is a natural target. A PHP file placed there, often called a web shell or backdoor, can then be run simply by visiting its URL. From there, the attacker can read your database credentials, create admin users, inject spam, or plant more malware.
Blocking PHP execution in the uploads folder breaks that chain:
- Uploaded PHP files can't run: Even if a malicious file lands in the folder, the server refuses to execute it when someone requests it.
- Backdoors become useless: A web shell that can't be executed can't do any damage from the browser.
- It limits the impact of plugin vulnerabilities: Many file upload vulnerabilities rely on executing the uploaded file. This hardening neutralises that step.
- It costs almost nothing: Legitimate media files don't need PHP, so the rule rarely breaks anything.
This is defence in depth. It doesn't fix the vulnerability that allowed the upload, but it stops that vulnerability from becoming a full site compromise.
Before You Start
A few quick preparations make this process safe:
-
Take a backup: Back up your files and database first. Server configuration mistakes can cause errors, and you want an easy way back.
-
Find out which web server you use: Check your hosting dashboard, ask your host, or look at the
Serverresponse header. Apache and LiteSpeed read.htaccessfiles. Nginx does not. -
Get file access: You'll need SFTP, SSH, or your host's file manager to create or edit files.
-
Check for plugins that store PHP in uploads: A small number of plugins write PHP files into subfolders of
uploads, such as some caching or form plugins. After applying the block, test the plugins you rely on.
Method 1: Block PHP in Uploads With .htaccess (Apache and LiteSpeed)
On Apache and LiteSpeed servers, an .htaccess file applies to the folder it sits in and every folder below it. That makes it perfect for this job.
Step 1: Create the .htaccess File
Using SFTP or your host's file manager, go to wp-content/uploads/. If there's already an .htaccess file there, download a copy before editing it, since some plugins place rules there. Otherwise, create a new file named .htaccess (with the leading dot).
Step 2: Add the Deny Rule
Add the following rules:
# Block execution of PHP and related script files in uploads
<FilesMatch "\.(?i:php[0-9]?|phtml|phar|pht|phps)$">
Require all denied
</FilesMatch>
This uses Apache 2.4's Require syntax. The case-insensitive pattern matches .php, numbered variants like .php5 or .php7, and other extensions that some servers are configured to run as PHP, including .phtml and .phar. Any request for a matching file gets a 403 Forbidden response.
Step 3: Save and Test
Save the file, then follow the testing steps later in this article to make sure the rule is working.
Why Not Use php_flag engine off?
You may see older guides recommend this:
php_flag engine off
That directive only works when PHP runs as an Apache module (mod_php). Most modern hosts run PHP through PHP-FPM or LiteSpeed's LSAPI, where php_flag is either ignored or causes a 500 Internal Server Error. The FilesMatch approach with Require all denied works regardless of how PHP is run, which is why it's the better choice.
If the Rule Causes a 500 Error
If your site or media shows a 500 error after adding the file, your server probably doesn't allow the Require directive in .htaccess files. The AllowOverride setting in the main Apache configuration needs to include AuthConfig (or be set to All) for this to work. On shared hosting, contact your host. On your own server, check the Directory block for your site in the Apache configuration.
Catching Double Extensions
Some misconfigured servers execute files like image.php.jpg as PHP, because they treat any file containing .php as a PHP script. Correctly configured servers don't do this, but if you want an extra layer of protection, you can use a stricter pattern that matches .php anywhere in the filename:
# Stricter: also block names like shell.php.jpg
<FilesMatch "\.(?i:php[0-9]?|phtml|phar|pht|phps)(\.|$)">
Require all denied
</FilesMatch>
The trade-off is that a legitimate file with .php. in its name, which is rare, would also be blocked.
Method 2: Block PHP in Uploads With Nginx
Nginx ignores .htaccess files, so the rule has to go in your server configuration. You'll need SSH access, or you can ask your host to add it.
Step 1: Edit Your Server Block
Open your site's configuration file, which is typically located in /etc/nginx/sites-available/ on Ubuntu and Debian, or /etc/nginx/conf.d/ on RHEL-based systems such as Rocky Linux and AlmaLinux.
Step 2: Add the Location Block
Inside the server block, add this rule:
# Deny PHP execution in the uploads folder (including multisite paths)
location ~* ^/wp-content/uploads/.*\.(?:php[0-9]?|phtml|phar|pht|phps)$ {
deny all;
}
Placement matters. Nginx checks regular expression locations in the order they appear and uses the first one that matches. This block must come before your general PHP handler, which usually looks something like location ~ \.php$. If the general PHP block comes first, it will match the request and pass the file to PHP-FPM, and your deny rule will never be reached.
A typical order looks like this:
server {
# ... other settings ...
# 1. Block PHP in uploads first
location ~* ^/wp-content/uploads/.*\.(?:php[0-9]?|phtml|phar|pht|phps)$ {
deny all;
}
# 2. Normal WordPress routing
location / {
try_files $uri $uri/ /index.php?$args;
}
# 3. General PHP handler
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
}
}
Adjust the fastcgi_pass path to match your existing configuration.
Step 3: Test and Reload Nginx
Always check your configuration for errors before reloading:
sudo nginx -t
sudo systemctl reload nginx
If nginx -t reports an error, fix it before reloading. A reload with a broken configuration won't apply, but a restart could take your site down.
A Note on PHP-FPM Security
As an extra safeguard on servers you manage, make sure PHP-FPM only executes files with a .php extension. In your PHP-FPM pool configuration (for example, /etc/php/8.3/fpm/pool.d/www.conf), the security.limit_extensions directive should be set to .php:
security.limit_extensions = .php
This is the default in current PHP versions, but it's worth confirming. It prevents PHP-FPM from running files with other extensions even if Nginx passes them along by mistake.
Method 3: Block PHP in Uploads With a Security Plugin
If you don't have access to server files, or you prefer a dashboard setting, several reputable security plugins offer this hardening. On Apache and LiteSpeed, they usually work by writing the .htaccess rule for you.
- Solid Security: Includes a "Disable PHP in Uploads" option among its advanced or system tweak settings.
- Sucuri Security: Its Hardening section includes an option to block PHP files in the uploads directory, with a button to apply or revert it.
- All-In-One Security (AIOS): Offers file system and firewall settings that include protection for PHP files in uploads in recent versions.
Exact menu names vary between plugin versions, so search the plugin's settings for "uploads" or "PHP" if you can't find the option right away.
Keep in mind that on Nginx, plugins can't change your server configuration. They may show the setting as enabled, but it won't have any effect. On Nginx, use Method 2.
How to Test That PHP Execution Is Blocked
Never assume a security rule works. Test it with a harmless file.
- Create a test file: On your computer, create a file named
test-php-block.phpcontaining only this line:
<?php echo 'PHP is running'; ?>
-
Upload it with SFTP: Place it directly in
wp-content/uploads/. Don't upload it through the Media Library, since WordPress blocks PHP uploads there by default. -
Visit the URL: Open
https://yourdomain.com/wp-content/uploads/test-php-block.phpin your browser. -
Check the result: If you see a 403 Forbidden error, the block is working. If you see "PHP is running", PHP is still executing and the rule isn't active. If the browser downloads or displays the raw code, PHP isn't running either, but check your rule anyway.
-
Delete the test file: Remove
test-php-block.phpfrom the server as soon as you've finished testing.
You can also check from the command line with curl:
curl -I https://yourdomain.com/wp-content/uploads/test-php-block.php
The first line of the response should show HTTP/1.1 403 Forbidden or HTTP/2 403.
Check for Existing PHP Files in Uploads
Blocking execution stops future problems, but you should also check whether suspicious files are already there. The uploads folder normally contains no PHP files at all, apart from the occasional empty index.php that some plugins add to prevent directory listing.
If you have SSH access, list any PHP-like files in the uploads folder:
find wp-content/uploads -type f \( -iname "*.php*" -o -iname "*.phtml" -o -iname "*.phar" \) -print
For each file found, ask:
- Is it empty or does it only contain a comment? Files like
index.phpwith<?php // Silence is golden.inside are harmless placeholders. - Does it belong to a plugin you recognise? Check the folder name. Plugin-created folders usually have obvious names.
- Does it contain obfuscated code? Long strings of random characters, or functions like
eval,base64_decode,gzinflate, orassertused on encoded data, are strong signs of malware.
If you find something suspicious, don't just delete it and move on. Its presence means someone had a way to write files to your server. Scan the site with a malware scanner, update all plugins and themes, change your passwords and security keys, and treat it as a potential compromise.
Protecting Other Writable Folders
The uploads folder is the most important place to block PHP, but the same idea applies to other folders that only hold static files. For example, some plugins create folders under wp-content/ for cache files, backups, or logs. If a folder should never contain executable PHP, you can apply the same kind of rule to it.
Be careful not to apply this to wp-content/plugins/ or wp-content/themes/ as a whole. Some plugins and themes legitimately load PHP files directly through a URL, and blocking them would break those features. If you want to harden those areas, target specific subfolders after testing.
FAQ: Blocking PHP Execution in WordPress Uploads
No. Images, videos, PDFs, and other media are served as static files and don't need PHP. The rule only affects files with PHP-related extensions.
The Media Library rejects PHP files for most users because they aren't in the list of allowed file types. However, that doesn't help if an attacker writes a file through a vulnerable plugin or another route, which is why a server-level block is still valuable.
The most common reason is that your server runs Nginx, which ignores .htaccess files. It can also happen if the Apache configuration doesn't allow overrides for that folder. Check with your host.
Only if PHP runs as an Apache module. On PHP-FPM or LiteSpeed, it may be ignored or cause a 500 error. The FilesMatch rule with Require all denied works on any Apache 2.4 setup.
Yes. Multisite stores uploads in subfolders such as wp-content/uploads/sites/2/, and both the .htaccess rule and the Nginx location pattern apply to all subfolders.
Occasionally, yes. A few plugins write PHP files into uploads for caching or configuration. After adding the block, test your key plugins, and check their documentation if something stops working.
Conclusion
Blocking PHP execution in the WordPress uploads folder is one of the simplest and most effective hardening steps you can take. It takes a few lines in an .htaccess file on Apache or LiteSpeed, a single location block on Nginx, or one setting in a security plugin, and it turns a successful malicious upload from a full compromise into a harmless file that can't run.
Once the rule is in place, test it with a harmless PHP file, check the folder for any existing PHP files that don't belong, and keep your plugins and themes updated so attackers have fewer ways to write files in the first place. It's a small change that closes one of the most commonly abused paths into a WordPress site.


