Today I upgraded my webserver from Debian Buster to Bullseye and it was in fact a pretty straightforward upgrade. Everything seemed to work until I tried to reach a few WordPress sites on the server. At first I got some error about a missing MySQL module. The error message I got from PHPMyAdmin gave me a better clue: it said it was missing the mysqli module.

So I installed it with apt install php7.4-mysqli and that in fact made my WordPress sites work again.

The only problem now however, is that I can't update Wordpress. Everytime I try to update WordPress, I get an error:

WordPress Error

So what am I missing here? Do I need to install suphp (even though that wasn't installed under Buster)?

EDIT: I took me quite a while to figure out what actually was going on. Now I know, I have no idea how to solve the problem.

The error message WP is giving, is actually incorrect. As it turns out, it is able to unpack the update just fine in the proper folder. But it’s when it checks if the files have actually unpacked, where it goes wrong. The problem lies in this piece of code in update-core.php:

foreach ( $roots as $root ) {
  if ( $wp_filesystem->exists( $from . $root . 'readme.html' )
    && $wp_filesystem->exists( $from . $root . 'wp-includes/version.php' )
  ) {
    $distro = $root;
if ( ! $distro ) {
  $wp_filesystem->delete( $from, true );
  return new WP_Error( 'insane_distro', __( 'The update could not be unpacked.' ) );

What it does here, is simply check if two files exists in the folder it has just unpacked the zip file to. This fails. And the reason is as follows:

I use FTP method for installing updates. So when I tell it to update, it first figures out the folder it should download the zipfile to. This folder is stored in $working_dir and is used from that moment on for the rest of the update process. The true path on the server is /domains/domainname.com/htdocs/wp-content/upgrade/ but since FTP users are chrooted, WP finds and stores /htdocs/wp-content/upgrade/ instead. The update file is downloaded to this folder and then unpacked.

Next it does the above check. And that fails because it tries to find a file in /htdocs/wp-content/upgrade/ while the true location is /domains/domainname.com/htdocs/wp-content/upgrade/.

I understand why it downloads the package just fine (since FTP users are chrooted). But I don’t understand why it doesn’t fail unpacking afterwards but it does fail when checking for the existence of the files...

I have checked all php settings and can’t find anything different with the settings from before the Debian upgrade…

1 Answer 1


It took me a while to figure out exactly what was going on but it's actually a problem with WordPress. Bullseye installs version 1.0.49 of PureFTPd where in Buster v1.0.47 was installed. According to PureFTPd's changelog here, globbing was removed from the NLST command in v1.0.48 (which makes sense since it's actually not allowed according to the RFC).

The WordPress developers are aware of the problem and have patched the exists() function. The patch is available here; applying it is one of two ways to upgrade WordPress if your using PureFTPd version 1.0.48 or up and are stuck with upgrading through FTP.

You can also replace the function yourself in /wp-admin/includes/class-wp-filesystem-ftpext.php (or /wp-admin/includes/class-wp-filesystem-ftpsockets.php if you are using FTPSockets) with the following (which is actually my own implementation of the function):

        public function exists( $file ) {
          $retval = false;

          $list = ftp_nlist( $this->link, $file );
          if( ! empty( $list ) ) {
            // if ftp_nlist returns *something*, the file or directory exists, on any FTP server
            $retval = true;
          } else {
            // if ftp_nlist returns nothing, either the file/dir doesn't exist or it's a file and
            // the FTP server's NLST command doesn't support globbing (i.e. Pure-FTPD > v1.0.47)
            // Check if it'a file
            if( ftp_size( $this->link, $file ) >= 0 ) {
              $retval = true;
          return $retval;


WordPress 6.0 will have the new function by default.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.