If a plugin or theme update failed, the most likely state of your site is the one you had before you clicked the button. Since WordPress 6.3, core moves the current version into wp-content/upgrade-temp-backup/ before it writes the new one, and puts it back if the install returns an error.
That is the answer most guides skip. They list permissions, disk space and memory limits, which are real causes, but they never say what the site is actually running while you read the error message.
Three things decide that: whether this was an update or a fresh install, whether it was manual or automatic, and whether it was a plugin, a theme or core itself. They behave differently, and the differences are not documented anywhere obvious.
First, check which "update failed" you have
Two unrelated errors share the wording.
"Updating failed." in the block editor, while saving a post, is a REST API problem. Nothing was installed and no files changed. That is a firewall, a security plugin or a server rule blocking /wp-json/, and the fix is in Site Health, not in your plugins folder.
"Update failed: ..." on the Plugins or Updates screen is the one this post is about. Core tried to download an archive, unpack it, remove the old directory and move the new one in, and one of those steps returned an error.
Everything below is about the second one.
What core does when a plugin or theme update fails
WP_Upgrader::install_package() calls move_to_temp_backup_dir() before it touches the destination. The old plugin directory is moved (not copied) to wp-content/upgrade-temp-backup/plugins/<slug>, and themes go to .../upgrade-temp-backup/themes/<slug>.
If the install then returns a WP_Error, WP_Upgrader::run() registers restore_temp_backup() on the shutdown hook. Core's own comment explains why that hook and not an inline call:
Actions running on
shutdownare immune to PHP timeouts, so in case the failure was due to a PHP timeout, it will still be able to properly restore the previous version.
On success, the backup is deleted on shutdown too, at priority 100. Anything the cleanup misses is swept by a weekly cron event, wp_delete_temp_updater_backups, scheduled in WP_Upgrader::init().
So for a failed plugin or theme update, "nothing was updated and the old version is still active" is the designed outcome, and usually the real one. (Source: class-wp-upgrader.php and class-plugin-upgrader.php, WordPress trunk, read 2026-09-20.)
The three cases where nothing restores anything
This is where the guides that only list causes leave you exposed.
A fresh install has no backup. Plugin_Upgrader::install() passes a hook_extra of type and action only, with no temp_backup key, so the rollback path never arms. There is nothing to restore, which is fine, but a half-unpacked directory can be left behind.
A manual core update has no rollback. Core_Upgrader::upgrade() defaults attempt_rollback to false, and wp-admin/update-core.php calls it with only allow_relaxed_file_ownership. The automatic background updater passes 'attempt_rollback' => true; clicking "Update Now" does not. A failed manual core update can genuinely leave files half-replaced.
A manual plugin update that installs cleanly but then fatals is not caught. The fatal-error check is in WP_Automatic_Updater: it does a loopback request, calls has_fatal_error(), and only then calls restore_temp_backup(). That runs for automatic updates of active plugins. It does not run when you click the button yourself.
That last one is the gap worth remembering. Automatic updates are, in this one narrow sense, safer than manual ones.
Read the error code, because it says where it broke
The wording on screen is generic. The code behind it is not, and each one points at a different step.
download_failed: never reached your server. Outbound HTTPS, a firewall, or the source URL.incompatible_archive: downloaded, but the zip would not unpack or was empty.files_not_writable: core triedchmodon the old files, retried, and still could not write. The message names the files.remove_old_failed: the old directory could not be deleted. Usually ownership, not mode.fs_temp_backup_mkdir/fs_temp_backup_move: the backup step itself failed, so the install stopped before touching your working copy. Good failure.folder_exists/mkdir_failed_destination: leftovers in the destination from an earlier attempt.disk_full: literal.
fs_unavailable and the fs_no_* family mean core could not get a filesystem handle at all, which is the FTP credentials prompt in disguise.
If you cannot see a code, turn on WP_DEBUG_LOG and repeat the update. Our debug mode writes the log somewhere you can read without SSH.
Check what state you are actually in
Before you retry anything, find out what is on disk.
wp plugin list --update=available
ls -la wp-content/upgrade-temp-backup/plugins/
ls -la wp-content/upgrade/
A directory sitting in upgrade-temp-backup/plugins/ means a restore did not complete. The plugin directory in wp-content/plugins/ may be missing or partial. Move the backup back by hand before you do anything else.
wp-content/upgrade/ is the working directory. Stale folders there are harmless but they are evidence: they tell you an earlier attempt died mid-unpack.
For core, compare against the official checksums:
wp core verify-checksums
It reports every core file that differs from the release, which is the fastest way to tell a half-finished core update from a working one.
Retrying the same update usually fails the same way
If the first attempt failed because the files were not writable, or the disk was full, or PHP hit its time limit, the second attempt meets the same wall. Fix the cause, not the click.
The one genuinely useful retry is through WP-CLI, because it runs as your shell user rather than the web server user and prints the real error rather than a summary:
wp plugin update woocommerce
You can also check what a bulk update would touch without touching it:
wp plugin update --all --dry-run
Note that wp plugin update does not document any rollback or backup behaviour of its own, so treat a CLI update as the thing that can leave you with the new version and no easy way back.
The honest version: pin the version you know works
If an update keeps failing and you need the site working now, install the known-good version explicitly rather than fighting the updater:
wp plugin update woocommerce --version=9.4.2
That is a holding position, not a fix. A plugin left behind on an old version is a security problem in a few weeks' time.
Test the update somewhere you do not care about
The reason a failed update is frightening is that the only copy of the site is the live one. That is the actual problem, and it is fixable.
Clone the site, run the update on the clone, and watch what happens. If it fatals, you learn that on a copy nobody is looking at, with WP_DEBUG already on and a log you can read.
SandyWP imports a live site into a disposable sandbox, and SSH and SFTP access means you can inspect upgrade-temp-backup/ directly when something goes wrong. The WP-CLI access is the same wp binary, so the commands above run unchanged. That workflow is what /for/upgrade-testing exists for.
When a sandbox is the wrong answer
If the update fails because of your host's disk quota, file ownership, or an outbound firewall rule, a sandbox will not reproduce it. The update will succeed there and fail on production, and you will have learned nothing.
Those are host problems and belong in a support ticket. A sandbox tells you whether the new version of the code breaks your site. It cannot tell you whether your server will let you install it.
Split the question that way and both halves get easier. Test the code on a copy, and take the filesystem to your host.
Related reading
- WordPress stuck in maintenance mode, for when the failure leaves the
.maintenancefile behind. - "Another update is currently in progress", for the lock the updater takes.
- How to roll back a WordPress plugin, for the version you actually want back.
