← Back to blog

WordPress file permissions: why 755 and 644 are not enough

The SandyWP team 6 min read

The correct WordPress file permissions are 755 for directories, 644 for files, and 440 or 400 for wp-config.php where your host allows it. That answer is right, but it is only half of the problem, because WordPress does not decide whether it can update itself by reading those numbers.

It decides by checking who owns the files. A site can have perfect 755/644 modes and still ask you for FTP credentials on every plugin update.

The numbers, briefly

Each mode is three digits: owner, group, everyone else. Read is 4, write is 2, execute is 1, and you add them.

Path Mode Meaning
Directories 755 Owner can write; everyone can list and enter
Files 644 Owner can write; everyone can read
wp-config.php 440 or 400 Read-only, owner (and group for 440)
Anything 777 Never. Anyone on the server can write it

To set them from the WordPress root:

find . -type d -exec chmod 755 {} \;
find . -type f -exec chmod 644 {} \;
chmod 440 wp-config.php

Every guide on the first page covers this much. None of the ones we read go further, which is where the real support tickets come from.

How WordPress actually decides it can write

Before installing or updating anything, core calls get_filesystem_method() in wp-admin/includes/file.php.

We read it in the WordPress/wordpress-develop trunk on 2026-09-22. It does not look at modes at all.

It creates a temporary file named temp-write-test-... in wp-content. Then it compares two owners: the owner of file.php itself, and the owner of the file PHP just created.

If they match, WordPress uses the direct method and writes files through PHP.

If they do not match, it falls through to ssh2, then ftpext, then ftpsockets. That fallback is the "Connection Information" screen asking for an FTP hostname and password.

So the rule is simple and almost never stated: the files must be owned by the user PHP runs as. On Debian and Ubuntu that user is usually www-data.

In the official WordPress Docker image it is also www-data, uid 33.

This is why chmod 777 "fixes" nothing for the update screen. The temp file is still created by www-data, the core file is still owned by someone else, and the owners still differ.

Finding the mismatch in one minute

Check who PHP runs as, then who owns the files:

ps -o user= -C php-fpm | sort -u   # or: ps aux | grep -E 'php|apache|httpd'
ls -ln wp-admin/includes/file.php wp-content

ls -ln prints numeric uids, which avoids confusion when a container and its host name the same uid differently. If the owner is 0 (root) or your own login user, and PHP runs as 33, you have found the cause.

Then look for strays. One root-owned plugin folder is enough to make a single update fail while the rest work:

find . ! -user www-data -printf '%u %p\n' | head -50

The fix is chown, not chmod

Give the files back to the PHP user, then set the modes:

sudo chown -R www-data:www-data /var/www/html
find /var/www/html -type d -exec chmod 755 {} \;
find /var/www/html -type f -exec chmod 644 {} \;

Reload the plugins screen. The FTP prompt disappears, because the two owners now match.

If you share the server with other sites or other people, that is a real tradeoff, because making PHP the owner means a compromised plugin can rewrite core files.

Some admins deliberately keep files owned by a deploy user and accept the FTP prompt, or update only through a deploy pipeline. That is a sound choice, as long as it is a choice.

What about FS_METHOD?

You can skip the ownership check by adding this to wp-config.php:

define( 'FS_METHOD', 'direct' );

Core reads the constant first and never runs the owner comparison. It is the most copied fix for the FTP prompt.

It is also the one we would use least, because it hides the mismatch instead of fixing it.

Updates will then fail partway with Could not create directory or Could not remove the old plugin, which is a worse error than a prompt. We covered what those failures leave behind in WordPress update failed.

Use FS_METHOD only when you know PHP can write every path, for example in a container where you control the image.

Where new files get their mode

When WordPress does write, it sets modes from two constants. In WP_Filesystem(), core defines them if you have not:

define( 'FS_CHMOD_DIR', ( fileperms( ABSPATH ) & 0777 | 0755 ) );
define( 'FS_CHMOD_FILE', ( fileperms( ABSPATH . 'index.php' ) & 0777 | 0644 ) );

The bitwise OR means new files are at least 755 and 644, plus whatever your WordPress root and index.php already allow.

If someone once set the root to 775, every plugin update inherits group write. Set the constants explicitly if you want a stricter floor.

The most common cause: running things as root

In our experience running sandbox infrastructure, the mismatch is almost never created by WordPress. It is created by a person or a script with more power than PHP.

Three habits do it:

  • wp with --allow-root. WP-CLI refuses to run as root without that flag for exactly this reason. Every plugin it installs is then owned by root, so run it as the web user instead: sudo -u www-data wp plugin update --all.
  • docker exec into a container. It opens a root shell by default, and anything you unzip or copy there is root-owned. Use docker exec -u 33 ....
  • Uploading over SFTP as your login user. The files belong to you, not to PHP, unless the server maps them.

The failure is quiet. A root-owned wp-content/uploads/2026/09 folder means media uploads fail for that month only, with a vague "Unable to create directory" message.

Test permission changes on a copy first

Permission fixes are recursive commands run with sudo, which is exactly the kind of change you want to rehearse. A wrong path in chown -R is hard to undo.

A copy made with the Cloner plugin gives you the same files in a sandbox. There you can run the find commands above, apply the fix, and confirm a plugin update goes through without the FTP prompt.

SandyWP sandboxes run the official WordPress image, and paid plans include a real shell over SSH and SFTP, so the commands in this post work as written.

If you break the copy, delete it and clone again. Then run the same commands on production, knowing what they will change.

Quick answers

Should wp-config.php be 400 or 600? 400 or 440 if PHP can still read it. Where PHP runs as a different user than the owner, 440 with a shared group is what works, so test it: a blank site means PHP cannot read the file.

Why does one plugin fail to update when others work? Its folder has a different owner. Run the find ! -user command above.

Is 775 acceptable? Only if the group is limited to users you trust. On shared hosting, treat group write like world write.

Does this apply on Windows or IIS? Partly. The owner check still runs, but IIS permissions are ACLs rather than Unix modes, so ask your host.

For the broader habit of testing risky changes before production, see how to test a plugin update safely and our upgrade testing guide.