WordPress asking for FTP credentials on plugin install: how to fix it

You click Install Plugin, and instead of installing, WordPress shows a Connection Information form asking for an FTP host, username, and password. This article explains the file ownership check that triggers it, walks through the correct fix (chown to the PHP user), shows the wp-config.php shortcuts and when each one is safe, and lists the common Nginx and Docker scenarios where it shows up.

You click Plugins > Add New, pick a plugin, hit Install Now, and instead of the install progress bar you get a form titled Connection Information asking for Hostname, FTP Username, FTP Password, and a choice between FTP and FTPS. The same screen appears when you click Update Now on a plugin or theme. Filling in the form often does not even work, because the host has no FTP service running. The fix is almost never to enter credentials. The fix is to make the file ownership check pass.

What the FTP credentials prompt actually means

WordPress is not asking you for credentials because it loves FTP. It is asking because a runtime check inside get_filesystem_method() decided that the PHP process running WordPress does not own the WordPress files, and therefore cannot safely write to them directly.

The relevant function lives in wp-admin/includes/file.php and runs every time WordPress is about to install or update a plugin, theme, language pack, or core. Stripped down, it does this:

// Create a temporary file in wp-content
$temp_file_name = $context . 'temp-write-test-' . uniqid( '', true );
$temp_handle    = @fopen( $temp_file_name, 'w' );

if ( $temp_handle ) {
    // Compare the UID that owns wp-admin/includes/file.php
    // against the UID that just wrote the temp file
    $wp_file_owner   = @fileowner( __FILE__ );
    $temp_file_owner = @fileowner( $temp_file_name );

    if ( false !== $wp_file_owner && $wp_file_owner === $temp_file_owner ) {
        $method = 'direct';
    }
}

The logic is: if the user that owns the WordPress core files is the same user that just successfully wrote a temp file inside wp-content/, then PHP itself is running as the file owner, which means PHP can directly create, modify, and delete plugin files without any external help. WordPress sets the method to direct and the install proceeds silently.

If the two UIDs do not match, the check falls through. WordPress then looks for an SSH2 connection, then the FTP PHP extension, then PHP socket-based FTP, and if none of those are pre-configured it falls back to prompting you for FTP credentials. That is the form you are looking at.

The decision priority is documented under WP_Filesystem in the Advanced Administration handbook: direct, then SSH2, then FTP extension, then FTP sockets, then prompt.

So the question is never "what FTP credentials should I type into this box". The question is "why do the file owner UIDs not match", and the answer is almost always one of the same three setups.

The two diagnostic questions

Before you change anything, answer these two questions on the actual server. They tell you which fix you need.

Question 1: who owns the WordPress files?

# Replace /var/www/html with your WordPress install path
stat -c '%U %u %n' /var/www/html/wp-admin/includes/file.php

The output will look something like deployer 1001 /var/www/html/wp-admin/includes/file.php. The first column is the username, the second is the numeric UID. Write both down.

Question 2: who runs PHP?

The answer depends on how PHP is wired up. The most reliable check is to ask PHP itself:

# From the server, run a one-line script as the web server user
sudo -u www-data php -r 'echo posix_getpwuid(posix_geteuid())["name"], " ", posix_geteuid(), PHP_EOL;'

Or, if PHP-FPM is in use, look at the pool config:

# Find the PHP-FPM pool file and grep the user/group lines
grep -E '^(user|group)' /etc/php/8.2/fpm/pool.d/www.conf

A default Ubuntu install of php8.2-fpm from the package manager runs the pool as www-data UID 33. A custom pool, a per-vhost pool, a Docker container running the official wordpress:php8.2-fpm image, and a cPanel or Plesk install with suexec all give you something different. Find the actual UID before you assume.

If your two answers match, the FTP prompt is being triggered by something else (see the section below on open_basedir and disable_functions). If they do not match, you have the most common case: the file owner is a deploy user, and PHP runs as the web server user.

Fix 1: change file ownership to the PHP process user

This is the architecturally correct fix. It addresses the root cause, it does not weaken security, and it removes the prompt for good.

Once you know who runs PHP from question 2, change the ownership of the WordPress directory tree to that user:

# Change owner and group of the entire WordPress install
sudo chown -R www-data:www-data /var/www/html/

# Confirm the change
stat -c '%U %u %n' /var/www/html/wp-admin/includes/file.php

Now reload Plugins > Add New in wp-admin and click Install Now again. The Connection Information form is gone and the plugin installs directly.

You will know it worked when the plugin install progress bar appears immediately, no Connection Information form is shown, and the new plugin lists in Plugins > Installed Plugins within a few seconds.

There is one caveat to be honest about: if you also deploy code to this directory through a CI pipeline, an SSH session, or an SFTP login as a different user (typically deployer or your own login name), changing ownership to www-data means that user can no longer write files without sudo. The clean answer is to put both your deploy user and www-data in a shared group, set the directory mode to 775, and turn on the setgid bit so that newly created files inherit the group. That is the same setup WordPress documents on the file permissions page, and it is covered in detail in my guide on WordPress file permissions.

Fix 2: define FS_METHOD as direct in wp-config.php

If for any reason you cannot change file ownership (managed host, locked-down environment, deploy pipeline that re-chowns on every release), you can tell WordPress to skip the ownership check entirely and just use direct file I/O.

Open wp-config.php in your hosting panel's file manager (or download it via SFTP) and add this line above the /* That's all, stop editing! Happy publishing. */ comment:

// Skip the get_filesystem_method() ownership check
// and use direct file I/O for plugin and theme installs.
define( 'FS_METHOD', 'direct' );

When FS_METHOD is set, get_filesystem_method() returns its value immediately without running the temp file ownership comparison at all. WordPress goes straight to direct I/O, and the FTP form is never shown.

You will know it worked when the next plugin install completes without the Connection Information form. If the install fails with a permission denied error in wp-content/plugins/, that means PHP genuinely cannot write to the directory and the underlying problem is filesystem permissions, not the ownership check. At that point you need Fix 1.

The official wp-config.php documentation is candid about when this is dangerous: it warns that FS_METHOD = 'direct' is "fraught with opening up security issues on poorly configured hosts". Here is what that warning actually means, because it gets misquoted constantly.

The warning applies to shared hosts where every customer's site runs PHP under the same system user, typically apache or nobody. In that environment, if you force direct mode and your files are owned by that shared user, every other customer's PHP process can read and write your plugin files, and you have no isolation. That is genuinely bad.

The warning does not apply to a single-site VPS, a dedicated server, a per-customer PHP-FPM pool, a Docker container, a Kubernetes pod, or any modern managed host where PHP runs under a per-site or per-customer user. In those environments, FS_METHOD = 'direct' is the expected configuration. The setting is documented along with its sibling constants in my guide to wp-config.php.

The rule of thumb: if your hosting plan says "shared hosting" with no PHP isolation, stick with Fix 1 or talk to your host. If your hosting plan is a VPS, Docker, Kubernetes, or a modern managed WordPress host, Fix 2 is fine and usually the simplest path.

Fix 3: relaxed file ownership

There is a third option that is documented less and is worth knowing because the WordPress automatic background updater uses it.

Inside get_filesystem_method(), if the temp file owner does not match but the function was called with $allow_relaxed_file_ownership = true, WordPress still uses direct I/O, just with a different internal flag (relaxed_ownership instead of file_owner). You cannot turn this on globally from wp-config.php, but WP_Upgrader::fs_connect() passes it through for paths the background updater is allowed to write to.

The practical implication is that the WordPress background auto-updater can install minor core updates and security patches without the FTP prompt, even when manual installs from the dashboard hit it. If your site quietly installs 6.7.2 overnight but throws the FTP form when you click Add New, this is why.

There is no configuration constant for the manual case. The only way to take advantage of relaxed ownership is to call the upgrader API yourself with the flag set, which is plugin territory and not something you do from wp-config.php. For most readers, Fix 1 or Fix 2 is the answer.

Common Nginx and PHP-FPM scenarios on Ubuntu

A few specific setups account for most of the cases I see.

Default Ubuntu Nginx + PHP-FPM, files uploaded over SFTP as deployer. Files are owned by deployer:deployer. PHP-FPM pool runs as www-data:www-data. UIDs do not match. Either run chown -R www-data:www-data /var/www/html (Fix 1), or set FS_METHOD = 'direct' (Fix 2). On a single-site VPS, both are safe.

Per-vhost PHP-FPM pools, one pool per customer. This is the standard setup for managed hosting that runs Nginx with PHP-FPM. Each customer has a pool that runs as their own user (for example customer42). If the customer also uploads files as customer42, ownership matches and the prompt never shows. If the customer uploads as a different user (a developer's account, a deployment bot), ownership mismatches and the prompt appears. The fix is to make the upload user and the PHP pool user the same, which is a hosting panel setting, not a code change.

Docker, official wordpress:php8.2-fpm image. The image runs PHP-FPM as www-data UID 33. If you mount your WordPress files into /var/www/html from the host with bind mounts, the UID of the host files is preserved inside the container. If your host UID is 1000 (typical Linux desktop user), the container sees those files as owned by UID 1000, not 33, and the ownership check fails. The official docker-library/wordpress repository has a long-running thread about this in issue #298. The pragmatic fix in Docker is to either run the container as your host UID (--user 1000:1000), or set FS_METHOD = 'direct' in the WordPress config and chown the bind mount on the host once.

cPanel and Plesk shared hosts. These typically run PHP under suEXEC or PHP-FPM with a per-account user. WordPress core files are owned by your account user, PHP runs as that same user, the ownership check passes, and the FTP form does not appear. If you suddenly start seeing it on a host that previously worked, the most common cause is that a recent migration or restore changed file ownership. Your host's support can fix that with one chown command.

When to use FTP as the actual filesystem method

There is exactly one situation where typing real FTP credentials into the form (or hard-coding them in wp-config.php) is the right answer: when PHP genuinely cannot write to the WordPress directory and there is no path to making it possible. Some legacy shared hosts run this way on purpose. They expose an FTP daemon as the only write interface, and PHP never has write access to web content. In that environment, FTP mode is not a workaround, it is the supported install path.

If you are in that situation, the credentials block in wp-config.php looks like this and lives alongside the other wp-config.php settings:

define( 'FS_METHOD', 'ftpext' );
define( 'FTP_HOST', 'ftp.example.org' );
define( 'FTP_USER', 'username' );
define( 'FTP_PASS', 'password' );
define( 'FTP_BASE', '/path/to/wordpress/' );
define( 'FTP_CONTENT_DIR', '/path/to/wordpress/wp-content/' );
define( 'FTP_PLUGIN_DIR', '/path/to/wordpress/wp-content/plugins/' );
define( 'FTP_SSL', true );

For everyone else, FTP mode is a workaround that introduces a permanent dependency on a separate FTP service with its own failure modes. There is a known WordPress core regression that demonstrates exactly this risk, which is worth knowing about even though it is a different problem from the credentials prompt.

If you already have FS_METHOD = 'ftpext' set in wp-config.php and your automatic background updates stop working with a PHP fatal error after upgrading to WordPress 6.6 or later on PHP 8.0 or newer, you are looking at a separate, real bug that is not the same as the credentials prompt this article covers.

The bug is tracked in Trac ticket #62718. In WordPress 6.6, the auto-updater's maintenance_mode() call inside WP_Upgrader invokes WP_Filesystem() without arguments, which means the FTP connection is never initialised with credentials. On PHP 8.0 and later, the resulting call to ftp_nlist() receives null as its first argument and PHP throws a fatal error:

PHP Fatal error: Uncaught TypeError: ftp_nlist():
Argument #1 ($ftp) must be of type FTP\Connection, null given

The fix shipped in WordPress 6.8.1 RC1 and later releases. If you are stuck on an older 6.6 or 6.7 release and cannot update core, the workaround is to switch the filesystem method to direct (Fix 2 above), which avoids the broken FTP code path entirely.

To be completely clear: this Trac ticket has nothing to do with the initial Connection Information form that appears when you click Install Now. That form is the ownership check from get_filesystem_method(), which is the same logic that has existed for years. The Trac bug is a separate regression that only affects users who have already opted into FTP mode and rely on the background auto-updater. They are different problems with different fixes, and they should not be confused for each other.

Why chmod 777 is not the answer

Search results still recommend chmod -R 777 wp-content/ as a fix. It is a bad recommendation, and it is worth understanding why.

The ownership check inside get_filesystem_method() looks at UIDs returned by fileowner(), not at file mode bits. Setting the mode to 777 does not change ownership. The temp file gets created (because it would have been created anyway, since wp-content/ is normally writable by the web server), the UIDs are still compared, the comparison still fails, and WordPress still falls through to the FTP prompt. The only path by which chmod 777 can "fix" the prompt is via the relaxed ownership branch, and only if the upgrader code path that called the function set $allow_relaxed_file_ownership to true. For a manual install through the admin UI, that branch is not taken.

What chmod 777 definitely does is make every file in your wp-content tree world-writable. The WordPress file permissions documentation is explicit about what that means: "If a malicious user can upload a PHP file to your site, they have complete control over your blog." Any process on the same server, including a compromised PHP process belonging to a different site, can drop a backdoor in your wp-content/uploads/ directory. The correct file modes for WordPress are 644 for files and 755 for directories, and the article on WordPress file permissions explains the model in full.

Diagnose the ownership mismatch and apply Fix 1 or Fix 2. Do not chmod the problem.

Other things that can trigger the prompt

If the file ownership UIDs match but you still see the Connection Information form, two less common server configurations can produce the same symptom by causing the temp file write itself to fail silently.

open_basedir blocks the temp file directory. PHP's open_basedir setting restricts which directories PHP can read and write. If it does not include the wp-content/ directory the upgrader tries to use as $context, the fopen() call on the temp file fails, the ownership comparison never runs, and WordPress falls through to FTP. Check your open_basedir value in phpinfo() and confirm that the WordPress install path is inside it.

fileowner is in disable_functions. Some hardened PHP configurations disable POSIX-related functions. If fileowner() is in the disable_functions list, function_exists('fileowner') returns false inside get_filesystem_method(), both UIDs stay at false, and false !== false evaluates to false so the direct method is never selected. Check disable_functions in your PHP config and remove fileowner if it is listed.

Both of these are server-level settings. If your host controls them and refuses to change them, your only options are Fix 2 (FS_METHOD = 'direct') or moving to a host with a more reasonable PHP configuration.

When to escalate

If none of the above resolves it, hand the problem to your host or a WordPress specialist with this bundle in hand:

  • The exact text of the form, including the host field placeholder text if any (it can hint at which path was taken).
  • The output of stat -c '%U %u' /var/www/html/wp-admin/includes/file.php (or your install's equivalent path).
  • The output of ps -eo user,group,comm | grep php-fpm and grep -E '^(user|group)' /etc/php/8.2/fpm/pool.d/www.conf (adjust paths for your PHP version).
  • The PHP version (php -v) and the WordPress version (visible in Updates in the dashboard, or in wp-includes/version.php).
  • Your open_basedir and disable_functions from phpinfo().
  • Whether you have already tried setting FS_METHOD = 'direct' in wp-config.php, and what happened (the error message matters: a permission denied is a different problem from a silent fail).
  • The hosting type: VPS, dedicated, managed WordPress, shared with cPanel, Docker, Kubernetes.

A host can usually fix this in one chown command once they have those facts. Without them, the support thread becomes a guessing game.

How to prevent it from coming back

  • Keep your deploy user and your PHP user aligned. Whatever user runs PHP on your server should also be the user that owns the WordPress files. If you deploy as deployer and PHP runs as www-data, either change the deploy user, change the PHP user, or set both into a shared group with the setgid bit on directories.
  • Pin FS_METHOD = 'direct' in wp-config.php on isolated environments. On a VPS, Docker container, or Kubernetes pod where you control the PHP user, setting this constant once means the ownership check is never run again, and migrations or restores cannot accidentally re-trigger the prompt.
  • Audit ownership after every restore or migration. Any process that recreates files (a backup restore, an rsync from staging, a Git deploy that does not preserve ownership) can flip the UIDs out from under WordPress. After any of these, run the same stat command from question 1 and confirm the file owner matches the PHP user before someone clicks Install Now and discovers the form again.

Want WordPress hosting that stays stable?

I handle updates, backups, and security, and keep performance steady—so outages and slowdowns don't keep coming back.

Outsource WordPress maintenance

Search this site

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