
Table of Contents
Set directories to 755 and files to 644, owned by the deployment user with the web server group as needed, never 777. Verify by running a read and execute test as the web user (for example sudo -u www-data test -x /var/www/html/wp-admin && echo ok). If the site loads, the admin saves settings, uploads work, and the command prints ok without any world-writable bits, permissions are correct. Anything else means you still have a mismatch between owner, group, or missing execute bits.
Symptoms and Fast Triage Commands
When WordPress file permissions are wrong the failure modes are specific. You see a blank page with HTTP 500, a “403 Forbidden” on /wp-admin/, update prompts asking for FTP credentials, or uploads failing with “Unable to create directory wp-content/uploads/2026/01”. Start with these commands to locate the problem fast.
# See owner, group, and mode of core paths
ls -ld /var/www/html /var/www/html/wp-content /var/www/html/wp-content/uploads
ls -l /var/www/html/wp-config.php /var/www/html/index.php
# Find anything world writable (the 777 danger zone)
find /var/www/html -type d -perm -0002 -print
find /var/www/html -type f -perm -0002 -print
# Show the effective web user (Debian/Ubuntu Apache or Nginx)
ps aux | egrep '(apache|httpd|nginx|php-fpm)' | grep -v grep
If find lists paths, you have 777 or world-writable entries. If the admin asks for FTP, the web user cannot write to wp-content even though it may look writable to you as root. If directories lack x, traversal fails and you get 403 before PHP even runs.
What 755 and 644 Mean
Unix modes are three triplets for owner, group, and others. 755 is rwxr-xr-x: the owner can read, write, and enter (execute) the directory; group and others can read and enter but not write. Directories need the execute bit to be traversed, so every WordPress directory must be 755 or stricter. 644 is rw-r--r--: the owner can read and write the file, everyone else can only read. PHP source such as index.php, wp-config.php, and theme files should be 644 so the interpreter can read them but no other system account can modify them. The difference between 644 and 777 is that 777 adds write for group and others, which lets any compromised process on the host alter your code. Use stat -c '%a %n' file to print the numeric mode, and remember that wp-config.php at 600 is even safer when your control panel supports it.
Owner vs Web User
Two accounts matter: the owner (often your SSH/deploy user or root) and the web user that PHP-FPM or the server runs as (commonly www-data, nginx, or apache). WordPress writes to wp-content/uploads, applies core updates, and writes cache files as the web user. If the owner is deploy:deploy and directories are 755, the web user can read and traverse but cannot create files inside uploads unless it owns that path or the group permits it. The fix is not 777; it is aligning ownership. Run chown -R deploy:www-data /var/www/html/wp-content and chmod -R 775 /var/www/html/wp-content/uploads so the group www-data can write while the owner keeps control. For the rest of the tree keep 755/644 with owner deploy:deploy. If you deploy over SFTP as the same user that runs PHP, ownership is unified and permissions stay simple. When you use a managed panel, check which pool user PHP 8.3+ runs as before deciding the group.
Get a Quote
Email + requirement + budget range — we reply with a quote within 24 hours.
Symptoms of a Missing Execute Bit
A directory without the execute bit cannot be entered, even if it is readable. If /var/www/html/wp-admin is 644 instead of 755, Apache returns 403 Forbidden and Nginx logs “permission denied” on open(). PHP may also fail to include files, producing “failed to open stream: Permission denied” in wp-content/debug.log when WP_DEBUG is true. A missing x on wp-content breaks the admin entirely; a missing x on wp-includes breaks front-end rendering. Test with sudo -u www-data ls /var/www/html/wp-admin; if you get “Permission denied” while root can list it, the execute bit or ownership is wrong. Restore it with chmod 755 on every directory and confirm no file needs x unless it is a CGI script, which WordPress core does not use.
Step-by-Step Repair Process
- Back up current state: run
getfacl -R /var/www/html > perms-backup.txtor note owner and modes so you can roll back. - Find the web user with
ps aux | grep php-fpmand note the user and group. - Remove world-writable bits:
find /var/www/html -type d -exec chmod 755 {} +andfind /var/www/html -type f -exec chmod 644 {} +. - Set ownership for writable paths:
chown -R deploy:www-data /var/www/html/wp-contentwhere deploy is your user. - Allow group write on uploads:
chmod 775 /var/www/html/wp-content/uploadsand any cache dir likewp-content/cache. - Verify as the web user with the test commands in the next section and reload the site.
If the site breaks after this, restore from the backup: re-apply the exact chown lines you recorded or use setfacl --restore=perms-backup.txt. A common pitfall is running chmod -R 777 out of habit, which reintroduces the risk this guide exists to remove. Another pitfall is forgetting that some hosts use nginx not www-data; always confirm with ps first. Teams that standardize this during deployment can rely on our Custom WordPress Development workflow to bake correct modes into CI.
Verifying as the Web User

Running as root hides the real permission picture because root bypasses checks. To verify as the web user, use sudo -u to impersonate the account PHP runs under. This is the only reliable way to confirm the site can read config and write uploads.
# Confirm the web user can traverse and read core
sudo -u www-data test -r /var/www/html/wp-config.php && echo 'config readable'
sudo -u www-data test -x /var/www/html/wp-admin && echo 'admin traversable'
# Confirm the web user can write uploads
sudo -u www-data touch /var/www/html/wp-content/uploads/.perm-test && echo 'uploads writable'
sudo -u www-data rm /var/www/html/wp-content/uploads/.perm-test
# If PHP-FPM runs as another user, swap www-data for that name
sudo -u nginx test -x /var/www/html && echo 'nginx ok'
Each command should print its success line. If “config readable” fails, check group ownership and the 644 mode. If “uploads writable” fails, re-run the chown and chmod 775 steps. You can also drop a temporary PHP probe (<?php echo getmyuid(), ' ', get_current_user(); ?>) to print the runtime UID, but remove it immediately after testing. For teams deploying on Baota or LNMP stacks, our Deploy WordPress on Baota LNMP guide shows the same verification inside panel environments.
Why 777 Backfires
Setting 777 makes every file writable by every account on the machine. If a single unrelated service is compromised, the attacker can inject PHP into wp-content/plugins or rewrite index.php. Search engines and hosts may also flag the site, and some security plugins refuse to run. Because WordPress only needs write access to uploads and occasionally wp-content for updates, granting global write is unnecessary exposure. The correct model is least privilege: 755/644 everywhere, group-write only where the web user must create files. If you inherited a 777 site, repair it with the ordered process above and then monitor with find in a cron job. Keep a restore point using a tested method from our WordPress Site Backup and Restore reference so a permission mistake never becomes data loss.
Prevention and Lockdown
Put permission fixes into your deployment script so they run after every release. Add a weekly cron job: find /var/www/html -perm -0002 -print | mail -s 'World writable found' you@example.com. Use define('DISALLOW_FILE_EDIT', true); in wp-config.php to stop admin-based edits, and keep FS_METHOD unset so direct writes use the correct ownership. On Nginx with PHP 8.3 FPM, set listen.owner to match your deploy group in the pool config at /etc/php/8.3/fpm/pool.d/www.conf. Document the owner and group in your repo README so new staff do not guess. Review the official WordPress hardening documentation for the current 2026 baseline, and browse our technical column index for related server guides.
Action Checklist
- Run the triage
findandls -ldcommands to locate any 777 paths. - Reset directories to 755 and files to 644 across the whole install.
- Align owner and web-user group on
wp-contentanduploadsonly. - Verify with
sudo -uread, execute, and write tests as the PHP user. - Add a cron audit and bake the fix into your deployment pipeline.
Need expert help hardening your server and permissions? See our WordPress development services for audits, fixes, and ongoing care.
