WordPress file permissions explained (644, 755, and wp-config.php)

A plain-language explanation of WordPress file permissions, why the FTP credentials prompt appears when you try to install a plugin, and a reference table with the exact values for every path including wp-config.php.

Most WordPress permission advice collapses into two bad answers: "just run chmod 777" or "use the numbers in the docs and don't ask why". Neither helps when wp-admin pops up the "Please enter your FTP credentials" banner and you need to ship an update in the next ten minutes. This article explains what the three permission numbers actually encode, why WordPress sometimes asks for FTP credentials even though you are obviously logged in as an administrator, and the exact values for every path in a WordPress install including the one file that is almost always set wrong: wp-config.php.

What the three permission numbers mean

Linux and other Unix-like filesystems store three permission bits for each of three identities: the file's owner, the file's group, and everyone else ("others" or "world"). Each identity gets read (r, worth 4), write (w, worth 2), and execute (x, worth 1). The three numbers in 755 are a sum per identity: owner 7 = 4+2+1 (read + write + execute), group 5 = 4+1 (read + execute), others 5 = 4+1 (read + execute). The WordPress Advanced Administration Handbook documents this baseline mode.

On directories the execute bit does not mean "run this as a program". It means "allowed to traverse into this directory to reach files inside". A directory with mode 644 on the owner bit is unusable: the owner can read the directory listing but cannot enter it. That is why directories are 755 or 750 and files are 644 or 640. The differences between schemes come down to whether "others" (random users on the server, other shared-hosting tenants, compromised PHP workers) can read or traverse at all.

Separately from permissions there is ownership: every file has a single owning user and a single owning group, set by chown. Permissions describe what each of those three identities is allowed to do; ownership decides which identity the user running a process falls into. A file with mode 640 owned by deploy:www-data lets the deploy user read and write, lets any process running as www-data read, and lets no one else do anything. That is the shape you want for most WordPress files.

The table below is the canonical scheme from the WordPress Advanced Administration file permissions guide, consolidated into a single reference you can apply without switching between documents.

Path Directories Files Notes
/ (WordPress root) 755 644 Writable only by owner. The web server reads but should not write.
/wp-admin/ 755 644 No write access needed by the PHP process in normal operation.
/wp-includes/ 755 644 Same. Updates are written through the deploy user, not the web server.
/wp-content/ 755 644 PHP needs to write subdirectories here during plugin and theme installs.
/wp-content/uploads/ 755 644 Media library target. Must be writable by whichever user runs PHP.
/wp-content/plugins/ 755 644 Written by the deploy flow or, on suexec/PHP-FPM per-pool, by the PHP user during install.
/wp-content/themes/ 755 644 Same as plugins.
wp-config.php not applicable 600, 440, or 400 See the dedicated section below.
.htaccess (Apache only) not applicable 644 WordPress must be able to rewrite this when permalinks change.

A short vocabulary note on the two common environments:

  • Standard shared hosting with a separate FTP/deploy user and the web server running as a different user (www-data, nginx, apache): directories 755, files 644. The web server reads via the "others" bit.
  • Modern suexec or PHP-FPM per-pool setups where PHP runs as the same user that owns the files: directories 750, files 640. The "others" bit is dropped entirely because no third identity needs access. This is the tighter and more modern scheme, and it is the default assumption on properly-configured VPS hosting and most serious managed hosts.

Why chmod 777 is never the right answer

The WordPress permission docs dedicate an entire section, "The dangers of 777", to this misconception. Mode 777 grants read, write, and execute to the owner, the group, and every other user on the machine. On a shared host that means every other tenant's PHP process can overwrite your WordPress files. On a VPS it means any compromised process, under any user, can rewrite your site. Backdoors commonly persist by chmodding the file they injected to 777 precisely so that the compromise survives a password reset or a plugin update.

If a plugin install fails with a write error, the fix is never 777. It is either to correct ownership so the PHP process can write to the directory it legitimately needs, or to use the less permissive steps (756, 757) the handbook describes, or to address the root cause at the architecture level with PHP-FPM per-pool.

The special case: wp-config.php

wp-config.php holds your database credentials, your authentication keys and salts, and any secret API tokens you pass to plugins through constants. It is the single most sensitive file in a WordPress install. Leaving it at 644 is the most common and most understandable mistake, because 644 is the default for files and wp-config.php is just a file.

The canonical recommendation from the WordPress hardening guide is 440 or 400. Sucuri's wp-config guidance recommends starting at 400 and relaxing only if the environment requires it. The mental model per environment:

  • Standard shared hosting with PHP running as a shared web user (www-data, apache): set wp-config.php to 640 with the file owned by your deploy user and the group set to the web server group. That lets PHP read the file via the group bit without granting "others" any access, and it lets you edit the file as the deploy user without sudo.
  • suexec or PHP-FPM per-pool where PHP runs as the site owner: set wp-config.php to 440 or 400. The PHP process is the owner, so it reads via the owner bit, and no other identity needs access at all.

644 is not an HTTP exposure risk by itself because your web server executes .php files rather than serving them as source. That is the pragmatic defense the "644 is fine" camp leans on, and it is true as far as it goes. The problem is the other threat model. On shared hosting, every site on the same server runs as the same Apache user, and any compromised neighboring site can open your wp-config.php with file_get_contents() because 644 lets "others" read. On a misconfigured server where .php files are suddenly served as text, or where a backup tool leaves wp-config.php.bak sitting next to the live file, 644 means those copies are world-readable too. Tightening to 600 or 640 closes both doors without costing you anything functional.

You can verify the current permissions on wp-config.php through your hosting panel's file manager: right-click the file, choose "Change Permissions" or "Properties", and check the displayed octal value. It should read 600, 640, 440, or 400 depending on your environment. If you have SSH access, ls -la wp-config.php shows -rw------- for 600, -r--r----- for 440, or -r-------- for 400, with the owner matching the user that runs PHP (or, in the split-user model, with the group matching the web server group).

How ownership differs from permissions, and why it decides almost everything

Ownership is the quiet half of the permission story, and in WordPress it is the half that causes most of the visible problems. Two files can have identical mode bits and behave completely differently based on who owns them.

Consider a wp-content/plugins/ directory at mode 755. If it is owned by deploy:deploy, only the deploy user can write into it. If the PHP worker runs as www-data, it can list the directory (via "others" 5 = read + execute) but it cannot create new files inside. An attempt to install a plugin from wp-admin will fail, and WordPress will fall back to asking for FTP credentials. The permissions are "correct" by any textbook. The problem is entirely ownership.

The modern fix is not to chmod your way out of this. It is to align ownership with whichever user the PHP process runs as. On most shared and managed hosting, you cannot change file ownership yourself. Contact your host's support and ask them to verify that the PHP process user matches the file owner for your WordPress installation. On hosts with cPanel, the "Fix File Ownership" tool under "Additional Security" in WHM resolves this automatically.

If you have SSH access, the fix on a single-tenant VPS usually means chown -R www-data:www-data /var/www/html if your PHP-FPM pool runs as www-data. On a multi-site VPS with per-pool isolation, it means creating a dedicated system user per site and running both the PHP pool and the file ownership as that user. The PHP-FPM configuration manual documents the user and group pool directives that set this.

A rule of thumb that survives almost every environment: the user that owns WordPress's files and the user that runs PHP should be the same, and that user should never be a shared server-wide account like www-data on a shared host. On truly shared hosting without suexec, you do not get to enforce that rule. On a VPS or a modern managed host, you do, and you should.

Why WordPress asks for FTP credentials (and what FS_METHOD actually does)

The "Please enter your FTP credentials" prompt is the surface symptom of the ownership mismatch described above. The mechanism is documented in get_filesystem_method() in the WordPress reference. When WordPress needs to write a file during an update or plugin install, it calls this function, which:

  1. Creates a small temporary file in the target directory.
  2. Compares fileowner($temp_file) with fileowner(__FILE__) for WordPress's own PHP file.
  3. If the two UIDs match, WordPress concludes it can write safely as the current PHP user and returns 'direct'.
  4. If the UIDs do not match, WordPress concludes that writing as the PHP user would leave files owned by a different user than the rest of WordPress, which would break future updates, and falls through to 'ssh2', 'ftpext', or 'ftpsockets'.
  5. If none of those transports have pre-configured credentials in wp-config.php, WordPress shows the FTP credentials form.

That is the entire trigger chain. Notice what it is not: it is not a permission problem, it is an ownership mismatch. Setting chmod 777 on the directory does nothing useful because the check is against UIDs, not mode bits. Setting correct permissions without fixing ownership also does nothing. The only real fixes are to align ownership (the architectural answer) or to tell WordPress to skip the check with FS_METHOD.

The FS_METHOD constant added to wp-config.php looks like this:

// Skip the ownership check and write files as the PHP user.
// Only safe when the PHP user has genuine write access to wp-content.
define( 'FS_METHOD', 'direct' );

There is an important distinction to understand here, because it is widely misunderstood: FS_METHOD overrides WordPress's transport detection logic, not the kernel's permission enforcement. If you define FS_METHOD as 'direct' but the PHP process still does not have filesystem write access to wp-content, the direct write will fail with a raw filesystem error instead of a polite FTP prompt. FS_METHOD is a way to say "I know what I'm doing, trust me and try the write". It is not a way to grant permissions that are not actually there.

Related constants in wp-config.php:

// Mode bits WordPress applies when creating new files and directories.
// Defaults are 0644 and 0755 minus the process umask.
define( 'FS_CHMOD_FILE', ( 0644 & ~ umask() ) );
define( 'FS_CHMOD_DIR',  ( 0755 & ~ umask() ) );

These control the permissions WordPress applies to files and directories it creates during plugin or core updates. Values must be octal (leading 0, unquoted). The defaults are usually correct; the reason to override them is to match a host's stricter scheme, for example setting 0640 and 0750 on a suexec host that enforces those values through ACLs or a filesystem policy.

Setting permissions via your hosting panel, SFTP, or SSH

Most hosting control panels (cPanel, Plesk, DirectAdmin) include a file manager with a "Change Permissions" action in the right-click menu. The input box accepts the same three octal digits described above: set directories to 755, files to 644, and wp-config.php to the hardened value for your environment. On hosts like WP Engine, the host documents that they manage permissions for you at 0775 for directories and 0664 for files, so you do not need to set them at all, and attempting to set stricter values may be overwritten on deploy.

With an SFTP client (FileZilla, Cyberduck, Transmit), right-click a file or directory and pick "File permissions" or "Get Info" to set the mode. Apply 755 for directories, 644 for files, and the hardened value for wp-config.php. Do not use the "Recurse into subdirectories" option with a single value for the whole tree. You will end up with either directories at 644 (unreadable) or files at 755 (executable).

If you have SSH access, the entire tree can be normalized in two commands from the WordPress root:

# From the WordPress root directory. Replace /var/www/html with your path.
cd /var/www/html

# Directories get 755, files get 644.
find . -type d -exec chmod 755 {} \;
find . -type f -exec chmod 644 {} \;

# Tighten wp-config.php separately. Use 440 on suexec/PHP-FPM per-pool,
# or 640 if PHP runs as a shared web server user in your environment.
chmod 440 wp-config.php

The find pattern is safer than chmod -R 755 . because the recursive form would also set files to 755, making them executable. You want the owner execute bit only on directories.

When chmod is not enough: SELinux and ACL troubleshooting

There are two layers above POSIX permissions that can produce permission-like errors even when your mode bits and ownership are both correct. Both are worth a five-minute check when a write that should succeed is failing.

SELinux is a Mandatory Access Control layer enabled by default on Red Hat family distributions: RHEL, CentOS Stream, Rocky, Alma, Fedora, and Amazon Linux. SELinux applies policy on top of POSIX permissions and can deny a write that POSIX would allow. The WordPress file permissions guide flags this explicitly. Check whether it is active:

# Returns Enforcing, Permissive, or Disabled.
getenforce

# If Enforcing, list recent denials for the httpd or php-fpm domain.
ausearch -m AVC -ts recent

# Translate raw denials into the policy rules they violated.
audit2allow -w -a

A common WordPress-specific SELinux symptom is that .htaccess fails to update when permalinks change, or that media uploads succeed but later fail to read because the file context is wrong. The usual fixes involve setsebool -P httpd_unified 1 to allow the web server to both read and write, or chcon -R --type=httpd_sys_rw_content_t /var/www/html/wp-content/uploads/ to mark the uploads directory as HTTP-writable. Treat SELinux the same way you treat ownership: if chmod is not helping, SELinux is the next thing to check.

POSIX ACLs (Access Control Lists) are an extension to standard POSIX permissions, not a replacement. They let you grant access to specific additional users or groups beyond the single owner, single group, and world model. Some advanced hosting setups and enterprise managed platforms use ACLs in addition to mode bits to supplement the standard scheme, for example granting a deploy user and a backup user write access to the same tree without making either the owner.

You can tell ACLs are in use because ls -l shows a + at the end of the mode string:

$ ls -l wp-config.php
-rw-r-----+ 1 deploy www-data 4523 Apr 08 14:32 wp-config.php

The + means there are additional ACL entries beyond the three POSIX triples. Inspect them with getfacl:

getfacl wp-config.php
# file: wp-config.php
# owner: deploy
# group: www-data
user::rw-
user:backup:r--
group::r--
mask::r--
other::---

Standard chmod still works when ACLs are present, but it only sets the base POSIX bits. ACL entries are additive, and a too-permissive ACL can let a user in even when your mode bits look tight. If a site is on a managed host and chmod commands "don't stick" or a write succeeds under an unexpected user, get a getfacl output before you keep changing mode bits. Most self-managed WordPress installs do not use ACLs, but on the hosts where they do appear, they are invisible until you know to look for them.

For the broader security context that these permissions support, the companion article on WordPress security hardening covers the five controls that actually reduce risk in 2026, including how file permissions fit alongside 2FA, login rate limiting, and the DISALLOW_FILE_EDIT constant. If your permission problem is showing up as a blanket HTTP 403 rather than an FTP credentials prompt, the diagnostic flow is in 403 Forbidden in WordPress.

Want fewer security surprises?

Staying safe is routine work: patching, monitoring, backups and defense-in-depth—done consistently.

See WordPress maintenance

Search this site

Start typing to search, or browse the knowledge base and blog.