Wrong file permissions are the silent cause behind "white screen of death" errors, failed plugin updates, and mysterious upload failures. I've seen sites go live with 777 on everything—which works until the first security scan flags the server. This checklist walks you through the correct ownership and mode settings for every WordPress directory and file type, in the order you should check them.
Why permissions matter before launch
File permissions control two things: who can read, write, or execute a file, and whether the web server can modify your site. Too loose and you hand write access to any compromised plugin. Too tight and WordPress cannot write uploads or update itself.
The user running your web server—usually www-data, apache, nginx, or nobody—needs read access to all PHP files and write access to specific directories. Your SSH/SFTP user needs write access to deploy code and run WP-CLI. When these conflict, you see permission denied errors in PHP logs or failed deployments.
Most hosts set a default umask that creates files as 644 and directories as 755, which is fine for static content but breaks WordPress's expectation that it can write to wp-content/uploads. The fix is targeted: open write permissions only where WordPress needs them, and verify ownership matches your execution context.
Step 1: Verify the web server user
Before you touch any file, identify which user runs PHP requests. SSH into the server and create a test PHP file:
echo '<?php echo exec("whoami"); ?>' > /home/username/public_html/who.php
Visit yourdomain.com/who.php in a browser. The output is your web server user. Delete the file immediately after:
rm /home/username/public_html/who.php
On cPanel shared hosting this is usually your cPanel username. On a VPS running Apache it is often www-data or apache. On Nginx with PHP-FPM it is whatever user the FPM pool runs as, which you can check in /etc/php-fpm.d/www.conf or /etc/php/8.x/fpm/pool.d/www.conf.
Write this username down. You will use it in the chown commands below.
Step 2: Set ownership on the WordPress root
All WordPress files should be owned by your deployment user (the account you use for SFTP/SSH), and the group should be either that same user or the web server user. This lets you deploy via SFTP and lets the web server read files.
sudo chown -R youruser:www-data /home/username/public_html
Replace youruser with your SSH username and www-data with the web server user from step 1. If you are on shared hosting and do not have sudo, the host has likely already set ownership correctly; skip to step 3.
On some setups the group is your user's primary group instead of the web server group, and the web server relies on "other" read permissions. That is fine as long as directories are 755 and files are 644.
Step 3: Set directory permissions to 755
Directories need execute permission so the web server can list their contents and traverse into subdirectories. The standard is 755: owner can read/write/execute, group and others can read/execute.
find /home/username/public_html -type d -exec chmod 755 {} \;
This recursively finds every directory under your WordPress root and sets it to 755. It does not touch files. Run it from outside the WordPress directory or specify the full path.
Some guides suggest 750, which denies "other" access. That works if your web server user is in the file group, but breaks if the web server relies on other permissions. Stick with 755 unless you have a specific security policy.
Step 4: Set file permissions to 644
Regular files—PHP scripts, CSS, JS, images—should be 644: owner can read/write, group and others can read. The web server does not need write access to code files, and it never needs execute permission on PHP files (the PHP interpreter handles that).
find /home/username/public_html -type f -exec chmod 644 {} \;
This hits every file. If you have shell scripts or binaries that need execute permission, you will fix those individually in step 12.
After this command, wp-config.php is 644, which is fine for most servers. Some hardening guides say to set it to 440 or 400, but that prevents you from editing it via SFTP and can break WP-CLI. If your threat model requires it, do it after you finish all other deployment tasks.
Step 5: Open write access on wp-content/uploads
WordPress writes uploaded media into wp-content/uploads. The web server user must be able to create directories and files here. You have two options: change ownership or open permissions.
Option A—change ownership of uploads to the web server user:
sudo chown -R www-data:www-data /home/username/public_html/wp-content/uploads
This makes uploads owned by the web server, which is clean, but means you cannot delete uploads via SFTP without sudo. If you are on shared hosting you do not have sudo, so use option B.
Option B—keep ownership as your user and set group write:
chmod -R 775 /home/username/public_html/wp-content/uploads
This gives the group write access. As long as the directory group is the web server user (from step 2), the web server can write. Your SFTP user can also write because it owns the files.
Pick one. I prefer option B on shared hosting and option A on VPS where I control sudo.
Step 6: Open write access on wp-content/cache
Caching plugins—W3 Total Cache, WP Super Cache, LiteSpeed Cache—write cache files into wp-content/cache. If the directory does not exist, create it:
mkdir -p /home/username/public_html/wp-content/cache
chmod 775 /home/username/public_html/wp-content/cache
Some plugins create subdirectories inside cache. If you already have cache directories, apply 775 recursively:
chmod -R 775 /home/username/public_html/wp-content/cache
If you are not using a caching plugin, skip this step. Creating an empty cache directory does not hurt, but it is not required.
Step 7: Open write access on wp-content/upgrade
WordPress unpacks plugin and theme updates into wp-content/upgrade before moving them to their final location. The directory might not exist on a fresh install. Create it with write permissions:
mkdir -p /home/username/public_html/wp-content/upgrade
chmod 775 /home/username/public_html/wp-content/upgrade
Without this, one-click updates fail with "could not create directory" errors. You can alternatively set ownership to the web server user if you used option A in step 5.
Step 8: Check plugin and theme directories
By default, wp-content/plugins and wp-content/themes are owned by your SFTP user and set to 755. This is correct if you deploy plugins via Git or SFTP. WordPress can read them but cannot write (install or update via admin).
If you want to install and update plugins from the WordPress dashboard, you must give the web server write access. Use the same ownership or permission pattern from step 5:
# Option A: change ownership
sudo chown -R www-data:www-data /home/username/public_html/wp-content/plugins
sudo chown -R www-data:www-data /home/username/public_html/wp-content/themes
# Option B: open group write
chmod -R 775 /home/username/public_html/wp-content/plugins
chmod -R 775 /home/username/public_html/wp-content/themes
I recommend keeping plugins and themes read-only (755) and deploying them outside WordPress. This prevents a compromised plugin from writing malware into other plugins. If you must allow dashboard updates, do it temporarily and tighten permissions after.
Step 9: Protect wp-config.php from web access
Your wp-config.php is already 644, which prevents direct writes but allows the web server to read it (required for PHP execution). The risk is if your web server is misconfigured and serves .php files as plain text, an attacker can download your database credentials.
Add this block to the .htaccess file in your WordPress root (or to your Apache virtual host config):
<Files wp-config.php>
Require all denied
</Files>
For Nginx, add this inside the server block:
location ~ /wp-config.php {
deny all;
}
This denies HTTP access to wp-config.php even if PHP execution is broken. The PHP interpreter can still read it because it accesses the file system directly, not via HTTP.
Step 10: Lock down .htaccess and .user.ini
If you are on Apache, WordPress may write to .htaccess for permalink rules. On PHP-FPM or LiteSpeed, it may write .user.ini for PHP settings. These files control server behavior, so they are high-value targets.
Make .htaccess writable only during initial permalink setup:
chmod 644 /home/username/public_html/.htaccess
If you need WordPress to modify it (e.g., when you change permalink structure), temporarily set it to 664 or 666, save your settings, then return it to 644.
For .user.ini, the same rule applies:
chmod 644 /home/username/public_html/.user.ini
Some hosts regenerate .user.ini automatically, in which case you can leave it alone. Check if changes you make persist after a few minutes. If they revert, the host is managing it.
Step 11: Verify no files are 777
Setting any file or directory to 777 (world-writable) is almost never correct and is a security audit failure. Search for them:
find /home/username/public_html -type f -perm 0777
find /home/username/public_html -type d -perm 0777
If either command returns results, fix them. Files should be 644 or 755 at most. Directories should be 755 or 775 at most. I have seen developers set 777 on wp-content to "fix" upload errors, which works but allows any user on a shared server to write malicious files into your site.
The correct fix for upload errors is step 5—targeted write access on the uploads directory only.
Step 12: Handle special cases
Some plugins and server configurations require exceptions.
WP-CLI scripts: If you have custom WP-CLI scripts in your site root, they need execute permission:
chmod 755 /home/username/public_html/custom-script.sh
Symlinked assets: If you symlink shared assets (e.g., a common uploads folder across multisite), the symlink itself has permissions 777 by default, but the target's permissions are what matter. Check the target:
ls -l /home/username/public_html/wp-content/uploads
If it shows lrwxrwxrwx (a symlink), follow it and verify the target directory is 755 or 775.
Session directories: Some plugins write PHP sessions to a custom directory. If you see errors about session paths, create the directory and set it to 1733 (sticky bit plus owner-only write):
mkdir -p /home/username/public_html/wp-content/sessions
chmod 1733 /home/username/public_html/wp-content/sessions
The sticky bit (1) prevents users from deleting each other's session files, which matters on shared hosting.
What to check first after going live
Once the site is live, watch for permission errors in your PHP error log. The two most common are "failed to open stream: Permission denied" (file or directory is too restrictive) and "file_put_contents: failed" (directory is not writable).
If WordPress cannot update itself or install plugins, confirm the web server user matches what you used in chown commands, and confirm wp-content/upgrade and the relevant plugin/theme directories have write access.
If image uploads fail, check wp-content/uploads ownership and mode. If they succeed but thumbnails do not generate, check that the web server can execute ImageMagick or GD, which is a PHP configuration issue, not permissions.
Run the find commands from step 11 weekly or after any manual file deployment. Permissions drift when you extract a zip or copy files with the wrong umask.
FAQ
What are the minimum permissions WordPress needs to run?
Directories 755, files 644, with write access (775 or ownership change) on wp-content/uploads, wp-content/cache, and wp-content/upgrade.
Should wp-config.php be 400 or 440?
Only if your security policy requires it and you have finished all deployment tasks. It prevents SFTP edits and can break WP-CLI depending on your setup.
Can I set everything to 777 temporarily to debug?
You can, but you must revert it immediately. Any compromised plugin can then write anywhere in your site, including dropping a PHP shell in your theme directory.
Why do some guides say 750 for directories?
That works if your web server is in the file group and you want to deny "other" access. On shared hosting where the web server relies on "other" read, 750 breaks your site.
How do I check current permissions?
ls -la /home/username/public_html shows ownership and mode for each file and directory. The first column is the mode (drwxr-xr-x), the third column is the owner, the fourth is the group.
Start at the root and work down
Set ownership first, then directory permissions, then file permissions, then open write access only where required. Verify no 777 permissions exist, protect critical files from HTTP access, and test uploads and updates before you mark the launch complete. Most permission issues show up immediately as white screens or failed uploads—fix them now instead of discovering them in a client support ticket.
Check your PHP error log daily for the first week. If you see permission denied errors, trace which file or directory caused them and apply the narrowest fix, not a blanket 777.
