2026年11月12日
2026年11月12日
WordPress File Permission Security: Find Risks, Limit Write Access
This article is a localized edition of the Japanese original. Read the Japanese original
When file permissions are too loose, a compromise elsewhere on the server makes it easier for an attacker to rewrite WordPress files or drop malicious PHP files. This article shows how to audit permissions from a security point of view and limit write access to only the places that need it. For the basics, such as what 755 and 644 mean, see Correct WordPress file permissions: 755/644 and how to check ownership. The information is current as of October 2026.
Back up your files and database before you start. Replace /var/www/html in the commands with your own WordPress folder.
The idea: fewer writers, fewer writable places
The official WordPress documentation, "Hardening WordPress", summarizes it like this.
| Location | Who can write |
|---|---|
The WordPress root folder (except .htaccess) |
Only your user account |
wp-admin/ and wp-includes/ |
Only your user account |
wp-content/ |
Your user account and the web server process |
wp-content/plugins/ |
Only your user account |
wp-content/themes/ |
Only your user account (plus the web server if you use the theme editor in the admin area) |
In other words, PHP (the web server) mainly needs to write to a few places inside wp-content/, such as uploads and caches. If PHP cannot rewrite anything else, the damage is smaller when PHP is abused through something like a plugin vulnerability.
However, updating plugins or core from the admin area requires PHP to be able to rewrite those files. How far you lock things down is a trade-off with the convenience of updating from the admin area (Step 3).
Step 1: Look for risky settings
Run the following commands over SSH. If nothing is printed, nothing matched.
cd /var/www/html
# Files and folders that other users (anyone on the server) can write to (777, 666 and so on)
find . -perm -0002 ! -type l
# PHP files inside uploads (normally there are none)
find wp-content/uploads -type f \( -iname '*.php*' -o -iname '*.phtml' -o -iname '*.phar' \)
# PHP files changed in the last 7 days (look for any you do not remember updating)
find . -type f -name '*.php' -mtime -7
# Permissions and owner of wp-config.php
stat -c '%a %U:%G %n' wp-config.php
How to read the results:
- Writable by other users: files and folders set to 777 or 666. You fix these in Step 2
- PHP files in uploads: PHP in a folder meant for images and other media is suspicious. Some plugins do place an
index.phpthere, but check the contents of anything you do not recognize and treat it as possible tampering - Recently changed PHP files: check for files changed on days when you did not update core or plugins
If you suspect tampering, WP-CLI can compare WordPress core files, and plugins distributed on WordPress.org, against the official checksums.
# Compare WordPress core files with the official checksums
wp core verify-checksums
# Compare plugins distributed on WordPress.org
wp plugin verify-checksums --all
wp core verify-checksums checks only core files. It does not look at themes or uploaded files inside wp-content/, so a clean result does not mean you are safe.
Step 2: Stop using 777 or 666 and return to the standard values
Set folders back to 755 and files to 644. Setting everything at once, as with chmod -R 755, makes every file executable, so set folders and files separately.
cd /var/www/html
find . -type d -exec chmod 755 {} +
find . -type f -exec chmod 644 {} +
777 means every user and process on the server can write to the file. If you use 777 because something does not work, the file can be rewritten when another site or another process is compromised. Do not use it even for temporary debugging. When something cannot be written, the cause is almost always a mismatched owner (Step 3).
Step 3: Limit writable places through ownership
Whether a file can be written depends not only on the permission numbers but on the combination of the file owner and the user that runs PHP. You can check which user runs PHP with the following command (lines for root are the master process, so look at the other lines).
ps -eo user,comm | grep -E 'php-fpm|php[0-9.]*-fpm|apache2|httpd' | sort | uniq -c
On shared hosting
On many shared hosting services, PHP runs as your own account, so PHP can rewrite every file. You cannot narrow things down by owner, so combine the values from Step 2, wp-config.php in Step 4, blocking PHP execution in Step 5, and disabling file editing in the admin area.
On a VPS where PHP runs as www-data or similar
Make a working user (for example deploy) the file owner, make the group the user that runs PHP (often www-data on Debian/Ubuntu), and grant group write access only to folders PHP needs to write to.
# Owner deploy, group www-data
sudo chown -R deploy:www-data /var/www/html
# Standard 755/644 (PHP can only read)
sudo find /var/www/html -type d -exec chmod 755 {} +
sudo find /var/www/html -type f -exec chmod 644 {} +
# Allow the group (PHP) to write only to the uploads folder
sudo find /var/www/html/wp-content/uploads -type d -exec chmod 775 {} +
sudo find /var/www/html/wp-content/uploads -type f -exec chmod 664 {} +
If PHP also writes to other folders, for example for a caching plugin (the plugin's documentation tells you which), set only those folders to 775/664 in the same way.
With this setup, PHP cannot rewrite core, plugin or theme files. In exchange, updating and installing from the admin area no longer works (you see the "Connection Information" screen), so you run updates with WP-CLI as the deploy user.
# Example of updating as the deploy user
cd /var/www/html
wp core update
wp plugin update --all
wp theme update --all
Also turning off the theme and plugin file editors in the admin area helps prevent PHP code from being written if an administrator account is taken over. Add this to wp-config.php above the line that says "That's all, stop editing!".
define( 'DISALLOW_FILE_EDIT', true );
Step 4: Tighten wp-config.php permissions
wp-config.php contains your database password. The official documentation recommends making it readable only by you (and the web server), which generally means 400 or 440. If PHP cannot read it, the site stops loading, so choose the value according to the owner.
| Situation | Example |
|---|---|
| PHP runs as the same user that owns the file (most shared hosting) | 400 (only the owner can read). Use 600 temporarily when you need to edit it |
The owner is a working user and PHP reads as the group (www-data) |
440 (owner and group can read). Use 640 temporarily when editing |
# When PHP runs as the file owner
chmod 400 /var/www/html/wp-config.php
# When running with owner deploy and group www-data
chmod 440 /var/www/html/wp-config.php
After changing it, confirm that the home page and the admin area load. If they do not, set it back to 644 and review the owner and group. Some shared hosting services restrict permission values, so also check your hosting company's guidance.
Step 5: Do not let PHP run in the uploads folder
Block it in the web server so that even if a PHP file is planted in uploads, it does not run.
Apache
Create wp-content/uploads/.htaccess (or add to it if it exists) with the following. This is Apache 2.4 syntax (Order and Deny from all are the old Apache 2.2 syntax). .htaccess must be allowed by the server configuration (AllowOverride).
<FilesMatch "\.(?i:php[0-9]?|phtml|phar)$">
Require all denied
</FilesMatch>
You may also see php_flag engine off, but it only works when PHP runs as an Apache module (mod_php) and cannot be used with PHP-FPM. The rule above works in both setups.
Nginx
Nginx does not read .htaccess. Add the rule to your site's server block. Nginx checks regular-expression location blocks in the order they are written and uses the first match, so put it before the location ~ \.php$ block that handles PHP.
location ~* ^/wp-content/uploads/.*\.(?:php[0-9]?|phtml|phar)$ {
deny all;
}
sudo nginx -t && sudo systemctl reload nginx
Once configured, place a test PHP file in uploads, open it in a browser, confirm it returns 403, then delete the file.
echo '<?php echo "PHP-RAN";' > /var/www/html/wp-content/uploads/test-deny.php
curl -s -o /dev/null -w '%{http_code}\n' https://example.com/wp-content/uploads/test-deny.php
rm /var/www/html/wp-content/uploads/test-deny.php
Step 6: Check regularly
Turn the checks from Step 1 into a script and run it daily. The results are kept in a log, and on servers that can send mail you can receive them through cron's MAILTO.
#!/bin/sh
# Check WordPress file permissions and print the results
WP_ROOT=/var/www/html
echo "== Files and folders writable by other users"
find "$WP_ROOT" -perm -0002 ! -type l
echo "== PHP files inside uploads"
find "$WP_ROOT/wp-content/uploads" -type f \( -iname '*.php*' -o -iname '*.phtml' -o -iname '*.phar' \)
echo "== Permissions and owner of wp-config.php"
stat -c '%a %U:%G %n' "$WP_ROOT/wp-config.php"
Save this as /usr/local/bin/wp-perm-check.sh, make it executable and register it in /etc/cron.d/wp-perm-check.
sudo chmod 755 /usr/local/bin/wp-perm-check.sh
# Run daily at 6:00 and overwrite the log with the results
echo '0 6 * * * root /usr/local/bin/wp-perm-check.sh > /var/log/wp-perm-check.log 2>&1' | sudo tee /etc/cron.d/wp-perm-check
If nothing is printed under any heading, there is no problem.
Cautions
- After changing permissions or ownership, check that image uploads, saving posts and (if you update from the admin area) plugin updates still work
- Creating WordPress files with
sudoor as root makes root the owner, which can later prevent PHP or your working user from rewriting them. Work as the owning user - Tight permissions do not prevent plugin vulnerabilities or leaked passwords. Also keep core, plugins and themes updated and protect administrator accounts
Summary
- Use
find . -perm -0002to find files anyone can write, andfind wp-content/uploadsto find PHP inside uploads - Return folders to 755 and files to 644, and do not use 777
- On a VPS, make a working user the owner and PHP the group, and allow writes only where needed, such as uploads. Update with WP-CLI
- Tighten
wp-config.phpto something like 400 or 440, as long as PHP can still read it - Block PHP execution in uploads in the web server. In Nginx, put the rule before the PHP
location - Script the checks and run them daily
Related articles:
- Correct WordPress file permissions: 755/644 and how to check ownership
- How to block PHP execution in the WordPress uploads directory with .htaccess
- Disable the WordPress theme and plugin file editors with DISALLOW_FILE_EDIT
Questions about WordPress? The publisher of this site is based in Japan and replies in Japanese. Contact (Japanese)