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.php there, 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 sudo or 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 -0002 to find files anyone can write, and find wp-content/uploads to 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.php to 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:

Questions about WordPress? The publisher of this site is based in Japan and replies in Japanese. Contact (Japanese)

お気軽にご相談ください

お見積りへ お問い合わせへ
▲