WordPress Asking for FTP Credentials? Fix the File Owner, Not the Permissions

Tested
On
WordPress 7.1.4, PHP 8.3, Apache 2.4 (Docker)
Compared
15 file owner, FS_METHOD and FTP setups
On this page: 11 sections

You click “Update now” on a plugin and, instead of updating, WordPress shows a “Connection Information” form:

To perform the requested action, WordPress needs to access your web server. Please enter your FTP credentials to proceed. If you do not remember your credentials, you should contact your web host.

The usual advice is to add define( 'FS_METHOD', 'direct' ); to wp-config.php or to chmod 777 your folders. I wanted to know what each of those really does, so I built a throwaway WordPress site in Docker, tried 15 different file owners and settings, and ran a dashboard update in each one. In this guide, I will show you what triggers the FTP prompt, how to check it on your own site and the fix that actually removes it.

The short answer

  • WordPress doesn’t only ask for FTP when it can’t write. Even when PHP can write, it asks if the files it would create wouldn’t have the same owner as its own files.
  • The fix is to make the user PHP runs as (usually www-data) the owner of the whole WordPress folder, not only wp-content.
  • chmod 777 doesn’t remove the prompt. FS_METHOD set to direct removes the prompt, but the update fails if PHP can’t write the files.
  • If you can’t change ownership (shared hosting), put your FTP details in wp-config.php and WordPress stops asking.

How I tested it

My test site ran WordPress 7.1.4 on PHP 8.3.36 with Apache 2.4.69, in the official wordpress Docker image (where Apache runs PHP as www-data, uid 33). I installed Akismet 5.3 so there was an update waiting (5.7.2), then for each setup I changed the owner and permissions of the files, logged in as the admin and opened the same “update now” link the Plugins screen shows (for the core updates, I used the button on Dashboard → Updates). For the “someone else’s files” case I used uid 1000, which is what your files look like when you upload them over SFTP as your own user.

Why WordPress asks for FTP

The check lives in get_filesystem_method() in wp-admin/includes/file.php. Simplified, it does this:

$temp_handle = @fopen( WP_CONTENT_DIR . '/temp-write-test-...', 'w' );
if ( $temp_handle ) {
    $wp_file_owner   = @fileowner( __FILE__ );        // owner of wp-admin/includes/file.php
    $temp_file_owner = @fileowner( $temp_file_name ); // owner of the file PHP just created
    if ( $wp_file_owner === $temp_file_owner ) {
        $method = 'direct';
    }
}
// otherwise: ssh2, then ftpext, then ftpsocketsCode language: PHP (php)

So two things have to be true for WordPress to update files by itself:

  1. PHP can create a file in wp-content.
  2. That new file gets the same owner as wp-admin/includes/file.php.

The second one is the part most people miss. It compares against a core file, not against wp-content, so giving only wp-content to the web server user isn’t enough.

What happened in each setup

SetupMethod WordPress picked“Update now” result
A. Everything owned by www-datadirectPlugin updated successfully.
B. Everything owned by uid 1000, 755/644ftpsocketsFTP credentials form
C. Same as B, plus chmod -R 777 wp-contentftpsocketsFTP credentials form
D. Core owned by uid 1000, wp-content owned by www-dataftpsocketsFTP credentials form
E. Same as D, plus FS_METHOD directdirectPlugin updated successfully.
F. Same as B, plus FS_METHOD directdirectCould not create directory.
G. Owner uid 1000, group www-data, 775/664ftpsocketsFTP credentials form
H. Same as G, plus FS_METHOD directdirectPlugin updated successfully.
I. chown -R www-data:www-data on the whole folderdirectPlugin updated successfully.
J. Plugin re-installed with WP-CLI running as rootdirectPlugin update failed.
K. Same as D, but a WordPress core update (7.1.3 → 7.1.4)ftpsocketsFTP credentials form
L. Same as K, plus FS_METHOD directdirectInstallation failed.
M. Core update after chown -R www-data:www-data on the whole folderdirectWordPress updated successfully.
N. Same as B, plus FTP details in wp-config.php (wrong password)ftpsocketsFTP credentials form, “Username/Password incorrect”
O. Same as B, plus FTP details in wp-config.php (right password)ftpsocketsPlugin updated successfully.

A few of these surprised me:

  • *C:* I made wp-content world-writable and WordPress still asked for FTP. PHP could create the test file, but the file belonged to www-data while file.php belonged to uid 1000, so the owners didn’t match. 777 gives you the risk of world-writable files without removing the prompt.
  • *D, K and L:* with only wp-content owned by www-data, plugin updates need FS_METHOD to work (E), and core updates fail even with it (L): “The update cannot be installed because some files could not be copied. This is usually due to inconsistent file permissions.: wp-admin/includes/update-core.php”. The PHP log had copy(/var/www/html/wp-admin/includes/update-core.php): Failed to open stream: Permission denied.
  • *J:* running wp plugin install ... --allow-root left the plugin folder owned by root (0:0). The FTP prompt didn’t come back, but the next dashboard update failed with “The update cannot be installed because some files could not be copied” and a list of every file in the plugin.

1. Check that it’s really the file owner

Go to Tools → Site Health → Status. When the owners don’t match, the “Background updates are not working as expected” item says:

Your installation of WordPress prompts for FTP credentials to perform updates. (Your site is performing updates over FTP due to file ownership. Talk to your hosting company.)

I got this in setups B and D. In setup A the test passed (“Background updates are working”).

Don’t check it with WP-CLI. I ran wp eval 'require_once ABSPATH . "wp-admin/includes/file.php"; echo get_filesystem_method();' as www-data in every plugin setup and it printed direct every time, even in B, where the dashboard asked for FTP. WP-CLI forces it. In WP-CLI 2.12.0, Runner.php adds a filesystem_method filter that always returns direct, so it can’t tell you what the dashboard will see.

2. Find the user PHP runs as

On a server where you have SSH, list the web server processes:

ps -eo user,comm | grep -E "apache|httpd|php-fpm|nginx" | sort | uniq -cCode language: Bash (bash)

On my test site it showed one apache2 process as root and six as www-data. The root one is only the parent process, the workers that run PHP are www-data. That’s because this image runs PHP inside Apache. If you see php-fpm processes, ignore the nginx or apache2 lines: only the user of the php-fpm workers (not the root master) matters. When in doubt, use the whoami.php check below.

If you can’t run ps, create a file called whoami.php in your WordPress folder with this line:

<?php echo posix_getpwuid( posix_geteuid() )['name'];Code language: PHP (php)

Open https://your-site.com/whoami.php in the browser. Mine printed www-data. Delete the file right after, there’s no reason to leave it public. If it shows an error page instead, your host has probably disabled the posix functions, so ask them which user PHP runs as.

3. Give the whole WordPress folder to that user

Replace the path with your WordPress folder and the user with the one you found above:

sudo chown -R www-data:www-data /var/www/htmlCode language: Bash (bash)

That’s setup I in my test: the prompt was gone and plugin updates worked. The core update went through too (M), without any FS_METHOD line. Keep the normal 755 for folders and 644 for files, you don’t need anything wider.

The trade-off: once www-data owns all the code, a hacked plugin can rewrite WordPress itself too. That’s the price of one-click updates. If it bothers you, look at the setup in the next section.

If WordPress runs in Docker

Here’s the trap I fell into while testing. I first ran chown -R www-data:www-data from the wordpress:cli container, and the FTP prompt stayed. In that image www-data is uid 82 (it’s Alpine), while in the wordpress image Apache’s www-data is uid 33 (it’s Debian). The same user name gave the files to a different user.

Run the chown inside the container that runs PHP:

docker compose exec wordpress chown -R www-data:www-data /var/www/htmlCode language: Bash (bash)

If the files are a folder bind-mounted from the host, you can also chown them on the host, but use the number, not the name. Check which number PHP uses first:

docker compose exec wordpress id -u www-dataCode language: Bash (bash)

It’s 33 in the regular wordpress image and 82 in the -alpine ones (I checked wordpress:fpm-alpine). Then, on the host, replace ./wordpress with your folder:

sudo chown -R 33:33 ./wordpressCode language: Bash (bash)

4. Run WP-CLI as the same user

Setup J showed how easy it is to break this again. One wp ... --allow-root command and the files it writes belong to root, so the next update from the dashboard fails. Run WP-CLI as the user PHP runs as. On a normal server that means sudo -u www-data wp plugin update --all instead of --allow-root. In Docker, watch out: the wordpress:cli image’s own www-data is uid 82, so on my test site the WP-CLI container is set to run as uid 33, the same user Apache uses for PHP:

  cli:
    image: wordpress:cli
    user: "33:33"
    volumes: [wp:/var/www/html]Code language: YAML (yaml)

If something already ran as root, the chown from step 3 fixes it.

If you want to keep your own user as the owner

Some people prefer their SSH user to own the files, so they can edit them without sudo. That works, but you need both of these:

  1. The files’ group is the PHP user’s group and it can write (775 for folders, 664 for files).
  2. define( 'FS_METHOD', 'direct' ); in wp-config.php, above the /* That's all, stop editing! */ line.

That’s setup H. Without the FS_METHOD line (G), WordPress still asked for FTP, because the owners don’t match. With it, the update worked, but the new plugin files belonged to www-data (33:33), not to my user. They kept the group write permission (664), so your user can still edit them if it’s in the www-data group.

The FS_METHOD line on its own is not a fix. In setup F (files owned by another user, not group-writable) it removed the prompt and the update failed with “Could not create directory.” instead.

If you can’t change the owner (shared hosting)

If your host doesn’t let you change ownership, you can give WordPress the FTP details once in wp-config.php instead of typing them on every update:

define( 'FTP_HOST', 'localhost' );
define( 'FTP_USER', 'your-ftp-user' );
define( 'FTP_PASS', 'your-ftp-password' );Code language: PHP (php)

Use localhost when the FTP server runs on the same machine as the site, so the password doesn’t leave the server (WordPress’s built-in FTP client doesn’t encrypt it). I tested this with an FTP server in a second container whose account owned the files and was rooted at the WordPress folder. With a wrong password (setup N), WordPress showed the form again with “Username/Password incorrect for deploy” (deploy was my FTP user). With the right one (O), the plugin updated without any prompt and the new files belonged to the FTP user, like the rest of the site. I didn’t need FTP_BASE, because the FTP account started in the WordPress folder. If yours starts higher up and the update can’t find WordPress, add define( 'FTP_BASE', '/path/to/wordpress/' ); (I didn’t test that one).

The downside is that your FTP password sits in plain text in wp-config.php, so use an FTP account that can only reach this site. If your host makes you do this, it’s also worth asking them whether PHP can run as your account user, which removes the problem for good.

That’s it! Click “Update now” again, and if you got the same results as me, the plugin updates without asking for anything.

Closing remarks

  • I tested Apache running PHP as www-data, the setup in the official Docker image. The check in get_filesystem_method() is the same everywhere, so with PHP-FPM the user that must own the files is the FPM pool’s user.
  • My test image has no ftp or ssh2 PHP extensions, so WordPress used its own FTP code (ftpsockets) and the form only offered FTP. If your server has the ssh2 extension, the form also offers SSH, which I didn’t test.

If you’re still getting the FTP form after fixing the owner, let me know in the comments what Site Health says and I’ll take a look.

More re-tested in WordPress

Leave a comment

Your email address won’t be published. Name, email and comment are required.