Skip to content
  • FG-19
  • 16 min read
  • Last checked November 8, 2026

How to Fix WordPress Stuck in Maintenance Mode and Failed Updates

Stuck on “Briefly unavailable for scheduled maintenance” or seeing “Update failed” or “Could not create directory”? Clear the update, then fix locks, ownership, disk space and downloads.

Run the health check

Your site shows a plain page that says Briefly unavailable for scheduled maintenance. Check back in a minute. and the minute has long passed. Or an update in the dashboard ended with one of these: Update failed, Could not create directory., Another update is currently in progress., Download failed., or a screen asking for FTP credentials before WordPress will install anything.

These all come from the same machinery: the WordPress updater, which downloads a zip file, unpacks it, swaps files on disk and cleans up after itself. When any part of that is interrupted or blocked, you are left with a maintenance flag, a lock, or a half-finished update. This guide is for anyone who looks after a WordPress site, from owners clicking Update in the dashboard to developers running updates over SSH. It takes you through clearing the stuck state safely and then fixing the reason the update failed, so the next one works.

What is happening

When you update a plugin, theme or WordPress itself, the updater does roughly this:

  1. Downloads the package into a temporary file and unpacks it into wp-content/upgrade/.
  2. Writes a file called .maintenance in your site folder containing the current time. While it exists, WordPress shows the maintenance message with an HTTP 503 status to everyone, including you in wp-admin.
  3. Since WordPress 6.3, moves the old plugin or theme into wp-content/upgrade-temp-backup/ so it can put it back if the update fails.
  4. Copies the new files into place, then deletes .maintenance and the temporary files.

Core updates also take a lock, stored as an option in the database, so two updates cannot run at once. These are the messages you may see and what each one means:

Message What it usually means
Briefly unavailable for scheduled maintenance The .maintenance file is still there, or something is re-creating it
Another update is currently in progress. The core_updater.lock option is still set from an earlier update
Could not create directory. PHP cannot write to wp-content or a subfolder: ownership, permissions, disk space or inodes
Download failed. The server could not fetch the package: outbound connection, DNS, TLS, or a license problem for premium plugins
A request for FTP credentials WordPress decided it cannot write files directly as the PHP user
Could not copy file / some files could not be copied Permissions on individual files, or the disk filled up mid-copy

One detail matters for diagnosis. WordPress ignores a .maintenance file once the time inside it is more than ten minutes old. So if your site has shown the message for much longer than that, something is either writing a fresh timestamp (a retrying automatic update, or a cron job) or a cache is serving a stored copy of the maintenance page.

Before you start

You need SSH, SFTP or your hosting file manager. Files that start with a dot are hidden by default, so turn on “show hidden files” in your FTP client or file manager. Before rolling anything back, take a backup of files and database; if wp-admin is unavailable, use your host’s backup tool or wp db export.

How to run the commands in this guide. Server commands run in an SSH terminal from your site folder, the one that contains wp-config.php. WP-CLI is called wp on most hosts and wp-cli on some. Run WP-CLI as the same user that owns the site files, never as root, or the files it writes will be owned by root and the next dashboard update will fail. curl commands that test your public site run on your own computer: built in on macOS and Linux, and curl.exe in Windows PowerShell or WSL.

Step 1: Clear the maintenance flag

ls -la .maintenance
cat .maintenance
wp maintenance-mode status

Run these over SSH in the site folder. ls -la shows the file with its size and modification time, and cat prints its content, which looks like <?php $upgrading = UNIX_TIMESTAMP; ?>. wp maintenance-mode status reports whether WordPress currently considers itself in maintenance. Good output: “No such file or directory” and a status saying maintenance mode is not active. Bad output: the file exists, or the status says it is active.

wp maintenance-mode deactivate

Run this over SSH. It deletes .maintenance. Good output: “Success: Deactivated Maintenance mode.” Without WP-CLI, delete the file over SFTP or in your file manager; it is safe to delete, because it contains nothing but a timestamp.

If the message comes back or never went away

  • A cache is serving it. Request the page with curl -sS -I "https://example.com/?nocache=RANDOM_STRING" from your own computer, replacing example.com with your domain and RANDOM_STRING with any new letters. If that returns 200 while the plain URL still shows maintenance, purge your caching plugin, server cache and CDN.
  • A plugin, not WordPress, is showing it. Maintenance and “coming soon” plugins, and some page builders, have their own maintenance switch. Check their settings, or deactivate them with wp plugin deactivate PLUGIN_SLUG. (A wp-content/maintenance.php file only restyles the core message; it does not turn it on.)
  • An update keeps retrying. If the file reappears with a new time, an automatic update is running repeatedly and failing. Steps 2 to 5 find out why.

Check what the interrupted update left behind

wp plugin list --fields=name,status,version,update
wp theme list --fields=name,status,version,update
wp core verify-checksums
ls -la wp-content/upgrade wp-content/upgrade-temp-backup/plugins wp-content/upgrade-temp-backup/themes

Run these over SSH. The first two show every plugin and theme with its status, installed version and whether an update is still pending. A plugin that was mid-update may be missing from the list entirely, or show as inactive. wp core verify-checksums checks core files against the official copies after an interrupted core update; good output is “Success: WordPress installation verifies against checksums.” The last command lists leftovers: unpacked packages in upgrade, and old copies of plugins and themes in upgrade-temp-backup. If a plugin disappeared, its previous version may be sitting there. See Step 6 for putting it back.

Step 2: Clear “Another update is currently in progress”

A core update writes the time it started into the core_updater.lock option and refuses to start another while that lock is less than fifteen minutes old. Background automatic updates use auto_updater.lock, which is honored for an hour. If PHP was killed mid-update, the lock stays behind.

wp option get core_updater.lock
wp option get auto_updater.lock
date -d @LOCK_TIMESTAMP

Run these over SSH. Each option holds a Unix timestamp. Replace LOCK_TIMESTAMP with the number the first command printed to see it as a readable date (on macOS use date -r LOCK_TIMESTAMP). “Could not get ‘core_updater.lock’ option” means there is no lock, which is good.

If the lock is older than the update could possibly still be running, and nobody else is updating the site right now, remove it:

wp option delete core_updater.lock
wp option delete auto_updater.lock

Run these over SSH. Good output: “Success: Deleted ‘core_updater.lock’ option.” Then retry the update, ideally from WP-CLI with wp core update, because WP-CLI prints the full error text when something fails.

Step 3: Fix file ownership, permissions and the FTP credentials prompt

Before installing anything, WordPress decides how to write files. It creates a temporary file and compares that file’s owner with the owner of WordPress’s own files. If they match, PHP writes files directly. If they do not, WordPress assumes it would leave files it cannot manage, and asks for FTP or SSH details instead. The same mismatch causes “Could not create directory.”

Confirm it

ls -ln wp-config.php wp-content wp-content/plugins wp-content/upgrade
id
ps -o user= -p $(pgrep -f "php-fpm: pool" | head -n 1)

Run these over SSH. ls -ln shows numeric owner and group IDs for key paths. id shows your own user and group. The ps line prints the user a PHP-FPM worker runs as, on servers where you can see those processes; on shared hosting it may print nothing, which is normal. Good output: the files, the folders and the PHP user all share one owner. Bad output: some paths owned by root or by a different user, often after someone ran WP-CLI or an unzip as root, or after a migration.

Fix it

chown -R SITE_USER:SITE_GROUP wp-content
find wp-content -type d -exec chmod 755 {} \;
find wp-content -type f -exec chmod 644 {} \;

Run these over SSH, on a server where you have the rights to change ownership (often as root on a server you manage; on shared hosting, open a ticket instead). Replace SITE_USER and SITE_GROUP with the user and group PHP runs as, from the checks above. -R applies the change to everything inside wp-content. The find lines set folders to 755 (owner can write, others can read and enter) and files to 644 (owner can write, others can read). Never use 777: it lets any process on the server write to your site. Keep wp-config.php at 640 or 600 if your server allows it. Good outcome: updates run with no FTP prompt.

About FS_METHOD

Many guides tell you to add define( 'FS_METHOD', 'direct' ); to wp-config.php. That forces direct writes and removes the FTP prompt, but it does not fix ownership. If PHP runs as a different user from the file owner, PHP may still be unable to write, or it creates new files owned by the PHP user that you cannot later edit over SFTP. Use it only when you understand who PHP runs as and that user already owns the files, for example in some containers where the ownership check gives a false result.

Step 4: Check disk space, inodes and the temp folder

An update needs room for the zip, the unpacked copy, the backup copy and the new files, all at once. A full disk produces “Could not create directory”, “Could not copy file” or an update that simply stops.

df -h .
df -i .
du -sh wp-content/* 2>/dev/null | sort -h | tail -n 10
wp eval 'echo get_temp_dir(), PHP_EOL;'

Run these over SSH in the site folder. df -h . shows free space on the disk holding your site, in readable units. df -i . shows inodes, the count of files the disk can hold; you can run out of inodes with gigabytes free, usually because of millions of cache or session files. The du line lists the ten largest items in wp-content, smallest to largest; 2>/dev/null hides permission warnings. The last line prints the folder WordPress uses for temporary downloads. Good output: comfortable free space and inodes (well under 90 percent used). On shared hosting your account quota in the hosting panel matters more than df, which may show the whole server.

Typical space hogs: old backup archives inside wp-content, cache folders, and leftover packages in wp-content/upgrade. Once no update is running, the contents of wp-content/upgrade are safe to delete. If the system temp folder is full or not writable, point WordPress at its own: create wp-content/temp and add define( 'WP_TEMP_DIR', ABSPATH . 'wp-content/temp/' ); to wp-config.php.

Step 5: Fix “Download failed”

The dashboard usually adds a reason after “Download failed.” Read it, because it names the cause.

Text after “Download failed.” Likely cause
cURL error 28: Operation timed out Outbound connection blocked by a firewall, or very slow
cURL error 6: Could not resolve host DNS lookups from the server are failing
cURL error 60 or 77: SSL certificate problem Outdated CA certificates on the server, or a broken PHP OpenSSL setup
Unauthorized, Forbidden, or a license message A premium plugin or theme whose license has expired or is tied to another domain
User has blocked requests through HTTP to the URL WP_HTTP_BLOCK_EXTERNAL is set in wp-config.php
curl -sS -o /dev/null -w "%{http_code}\n" https://downloads.wordpress.org/
wp plugin update PLUGIN_SLUG
grep -n "WP_HTTP_BLOCK_EXTERNAL\|WP_ACCESSIBLE_HOSTS" wp-config.php

Run these over SSH on the web server itself, because that is where the download happens. The curl line tests whether the server can reach the WordPress.org download host; a 3-digit HTTP code (even a redirect) means it can, while a timeout or “Could not resolve host” means it cannot. Replace PLUGIN_SLUG with the plugin’s folder name from wp plugin list; WP-CLI shows the full error message. The grep checks whether outbound HTTP is blocked on purpose. If WP_HTTP_BLOCK_EXTERNAL is true, either add the needed hosts to WP_ACCESSIBLE_HOSTS or remove the block. Outbound firewall and CA certificate problems need your host.

For premium plugins, re-enter or re-activate the license in the plugin’s settings, or download the zip from the vendor’s account area and install it with wp plugin install PATH_TO_ZIP --force, where PATH_TO_ZIP is where you uploaded the file.

Step 6: Roll back a bad update

Sometimes the update succeeded and the new version is the problem. Reinstall the previous version over the top:

wp plugin install PLUGIN_SLUG --version=VERSION_NUMBER --force
wp theme install THEME_SLUG --version=VERSION_NUMBER --force

Run these over SSH. PLUGIN_SLUG and THEME_SLUG are folder names from wp plugin list or wp theme list. VERSION_NUMBER is the release you want, such as the one you had before; the plugin’s page on WordPress.org lists previous versions under Advanced View. --version picks that release and --force overwrites the installed copy. This works for plugins and themes hosted on WordPress.org; for premium ones, use the vendor’s zip. Good output ends with “Success: Installed 1 of 1 plugins.” Settings stored in the database are kept, though a plugin that changed its database format during the update may not run cleanly on the old version.

Restore from upgrade-temp-backup

If a plugin vanished after an interrupted update and WordPress did not restore it automatically, its previous copy may be at wp-content/upgrade-temp-backup/plugins/PLUGIN_SLUG. Move it back to wp-content/plugins/ with SFTP or mv, then check it with wp plugin list.

Roll back WordPress core

wp core update --version=VERSION_NUMBER --force
wp core update-db
wp core verify-checksums

Run these over SSH. --version chooses the release and --force allows installing an older version than the one you have. wp core update-db runs any database upgrade the version needs, and the checksum check confirms the files match that version. Rolling back across a major version is riskier than within one, because newer versions can change the database; take a database backup first and prefer fixing forward where possible.

Fix “Destination folder already exists” and “The package could not be installed”

These two messages appear when you install a plugin or theme, usually by uploading a zip under Plugins, Add New, Upload Plugin (or Appearance, Themes, Add New, Upload Theme), followed by Plugin installation failed. or Theme installation failed.

“Destination folder already exists.”

WordPress refuses to unpack the zip because a folder with the same name is already in wp-content/plugins/ or wp-content/themes/. When WordPress recognizes the existing plugin, current versions offer a This plugin is already installed. screen with a button to replace it with the uploaded copy instead. So this error usually means the folder exists but WordPress does not see a working plugin or theme in it: a leftover from an interrupted install or update, an empty folder, or a copy missing its main file.

ls -la wp-content/plugins/PLUGIN_FOLDER
wp plugin list --fields=name,status,version

Run these over SSH in the site folder. Replace PLUGIN_FOLDER with the folder name, which is normally the zip file’s name without .zip (use wp-content/themes/ and wp theme list for a theme). The first command shows what is inside the folder; the second shows whether WordPress recognizes it. If the folder is empty or half-filled and the plugin is missing from the list or shown as inactive, it is safe to set the folder aside: rename it to PLUGIN_FOLDER.old with SFTP or mv, install again, check the site, then delete the .old folder. Plugin settings live in the database, not in this folder, so they are kept. To overwrite an existing copy in one step, run wp plugin install PATH_TO_ZIP --force, where PATH_TO_ZIP is where you uploaded the zip (or a WordPress.org slug) and --force replaces what is there. Good output ends with “Success: Installed 1 of 1 plugins.”

“The package could not be installed.”

WordPress unpacked the zip but did not find what it expected. Read the line after it, because it names the reason:

Line after the message What it means and the fix
No valid plugins were found. The zip is not a plugin. Usually it is a theme uploaded as a plugin, or the “full download” from a marketplace, which wraps the real plugin zip together with documentation and license files. Unzip it on your computer and upload the inner zip that contains the plugin’s folder.
The theme is missing the style.css stylesheet. The same problem the other way round: a plugin uploaded as a theme, or a theme package wrapped inside a bigger zip. Upload the inner theme zip.
Incompatible Archive. (sometimes with PCLZIP_ERR_BAD_FORMAT) The file is not a valid zip: the download was cut short, a browser renamed or unpacked it, or it was saved as an HTML error page. Download it again and check the file size matches what the vendor states.
Could not copy files. You may have run out of disk space. The disk or inode quota is full. See Step 4.

If the upload fails before any of these, with a message about upload_max_filesize or a 413 error, the zip is larger than the server accepts: see our guide on upload size limits, or upload the folder over SFTP into wp-content/plugins/ instead.

Advanced: control automatic updates deliberately

Many “stuck in maintenance” reports happen when nobody clicked anything: a background update ran overnight and failed halfway. Decide what updates automatically, instead of leaving it to defaults.

Setting Where Effect
AUTOMATIC_UPDATER_DISABLED true wp-config.php Turns off all background updates of every kind
WP_AUTO_UPDATE_CORE wp-config.php true for all core releases, 'minor' for minor and security releases only, false for none
DISALLOW_FILE_MODS true wp-config.php Blocks all plugin and theme installs and updates, in the dashboard and in the background
Per-plugin and per-theme auto-updates Plugins and Themes screens, or WP-CLI Enable or disable background updates item by item
wp plugin auto-updates status --all
wp plugin auto-updates disable PLUGIN_SLUG
wp option get auto_core_update_failed

Run these over SSH. The first lists which plugins update themselves. The second turns that off for one plugin; replace PLUGIN_SLUG with its folder name, or use enable to turn it on. The third shows whether WordPress recorded a failed background core update. After a “critical” failure, where files had started copying, WordPress stops attempting background core updates. Once you have updated core manually and confirmed the site works, delete that record with wp option delete auto_core_update_failed if it is still present, so background security releases can resume.

On sites where a broken update would be expensive, a sensible pattern is minor core updates on, plugin auto-updates only for small and well-maintained plugins, and everything else updated by hand on a staging copy first.

When to contact your host

Open a ticket if files are owned by the wrong user and you cannot change ownership, if the server cannot reach outside hosts, if CA certificates are out of date, or if you are out of disk space or inodes and cannot find the cause. Send:

  • The exact error text, ideally from WP-CLI output.
  • The time of the failed update in UTC.
  • The output of ls -ln on wp-content and df -h and df -i.
  • Which plugin, theme or core version you were updating, from and to.
  • What you already tried, such as removing .maintenance and clearing locks.

If the site is fully down rather than stuck on the maintenance message, start at the Emergency Room, and use our diagnostic tools to confirm what visitors see. If a plugin update broke your page builder layout rather than the update process itself, see our guide on page builders breaking after an update.

Still stuck?

Print a free one-page checklist to keep, or open the Emergency Room if the symptom does not match.

Get the free checklists

Keep reading

All field guides
FG-132 Updates

How to Update WordPress Plugins, Themes and Core Safely

A calm, repeatable update routine for WordPress: what auto-updates and core rollback protection really do, what order to update in, what to test afterward and how to roll one plugin back.

Read the guide
11 min read
FG-107 Updates

WordPress 7.0 Broke My Site: Known Issues and Fixes

Problems after WordPress 7.0? Learn what changed (PHP 7.4 minimum, iframed editor, pattern locking), which bugs 7.0.1 fixed, and how to roll back or report a bug.

Read the guide
10 min read

Search the field manual

What is going wrong?

Press / to search from any page, Esc to close