Harden WordPress file permissions and disable PHP execution in uploads

Incorrect file permissions and an unprotected uploads directory are two of the most exploited weaknesses on WordPress servers. If an attacker can upload a PHP file — through a vulnerable plugin, a weak password, or a misconfigured server — and the web server is allowed to execute PHP in the uploads folder, they have achieved remote code execution. The WordPress uploads folder (wp-content/uploads/) should never execute PHP: it stores images, PDFs, and other media, and there is no legitimate reason for any PHP file to be there. Blocking PHP execution in uploads via an .htaccess file is a one-line fix that eliminates an entire class of webshell attacks. File permissions follow a simple rule: directories should be 755 (owner can read/write/execute; group and others can read/execute) and files should be 644 (owner can read/write; group and others can only read). wp-config.php deserves extra protection at 440 or 400 since no web process needs to write to it. The web server user (www-data on Ubuntu/Debian, apache on CentOS) should own the WordPress files so WordPress can write to wp-content/ for plugin updates, but this ownership should be as narrow as possible. World-writable files (777) are a red flag — they mean any process on the server can modify that file. The find command makes it easy to audit and bulk-correct permissions across the entire WordPress installation. Review this alongside the HTTP security headers guide and the wp-config.php protection guide for a layered security approach.

Problem: Your WordPress uploads folder allows PHP execution, and your file permissions may be too permissive — both are common entry points for malware and webshell attacks.

Solution: Add an .htaccess file to the uploads directory to block PHP, and run the find commands below to set correct permissions across the entire WordPress installation:

# Set directory permissions to 755
find /var/www/html/wordpress/ -type d -exec chmod 755 {} \;

# Set file permissions to 644
find /var/www/html/wordpress/ -type f -exec chmod 644 {} \;

# Lock down wp-config.php — only the owner can read it
chmod 440 /var/www/html/wordpress/wp-config.php

# Verify: find any world-writable files (should return nothing)
find /var/www/html/wordpress/ -perm -o+w -type f

# Check web server ownership (adjust user to match your server)
chown -R www-data:www-data /var/www/html/wordpress/wp-content/

# Place this file at wp-content/uploads/.htaccess
# Blocks execution of PHP, Perl, and Python files in the uploads folder

<Files "*.php">
    Require all denied
</Files>

<Files "*.php5">
    Require all denied
</Files>

<Files "*.phtml">
    Require all denied
</Files>

<Files "*.pl">
    Require all denied
</Files>

# Also disable script execution via AddHandler
<FilesMatch "\.(php|php5|phtml|pl|py|cgi)$">
    SetHandler default-handler
</FilesMatch>

NOTE: After placing the .htaccess file in wp-content/uploads/, test it by trying to access a .php file in that directory directly via the browser — you should receive a 403 Forbidden response. On Nginx, add location ~* /wp-content/uploads/.*\.php$ { deny all; } to your server block instead, since Nginx does not read .htaccess files. Also check that your hosting environment does not automatically recreate a permissive .htaccess in uploads — some managed hosts do this. Combine this with the XML-RPC disable guide and username enumeration prevention for a hardened WordPress setup.