Why it matters
Check your site
This cannot be tested from outside without uploading a script, which is why the public check does not try. On your own server:
echo '<?php echo "php-runs";' > wp-content/uploads/nv-test.php
curl -s https://example.com/wp-content/uploads/nv-test.php
rm wp-content/uploads/nv-test.php
If you see php-runs, PHP executes in uploads. A 403 or 404, or the raw source, means it does not. Delete the test file either way.
Fix it
nginx
location ~* ^/wp-content/uploads/.*\.(php\d?|phtml|phar)$ {
deny all;
}Catch: nginx uses the first regular-expression location that matches, so this block must come before the general location ~ \.php$ block that hands requests to PHP.
Apache
Create wp-content/uploads/.htaccess:
<FilesMatch "\.(?i:php\d?|phtml|phar)$">
Require all denied
</FilesMatch>Catch: this needs AllowOverride to allow it for that folder; most shared hosts do. Run the test again to be sure.
Beyond uploads
Uploads is the obvious place, but a dropped script can also land in a plugin or theme folder. WordPress itself only needs a handful of PHP files reachable from the web: index.php, wp-login.php, wp-comments-post.php, wp-cron.php, and wp-admin. Refusing every other .php URL is stronger, but plugins that call their own PHP files directly need an exception, so try it in a log-only mode first.
Questions
Will this break image uploads?
No. Uploading goes through wp-admin, which is unaffected; only requesting a .php file inside the uploads folder is refused.
My host already blocks this. How do I know?
Run the three-line test above. Many hosts do block it, many do not, and it changes when a site moves.
Is this enough on its own?
It makes an uploaded script inert, which is the step most takeovers depend on. Keep plugins updated as well, since the upload itself still happened.
More guides
Last reviewed 2026-10-08