← Back to blog

WordPress stuck in maintenance mode: why it is rarely the .maintenance file

The SandyWP team 6 min read

If WordPress is stuck in maintenance mode, delete the .maintenance file in the site root (the folder that holds wp-config.php) and reload. That fixes it most of the time, and every guide on the first page says so.

What none of them says is that WordPress core already stops honouring that file after ten minutes. So if your site has shown "Briefly unavailable for scheduled maintenance" for an hour, the file is probably not what is holding it there, and deleting it may not be the whole repair.

The quick fix

Over SSH, SFTP or your host's file manager:

cd /path/to/wordpress
ls -la .maintenance
rm .maintenance

The leading dot hides the file in most FTP clients, so turn on "show hidden files" if you cannot see it. With WP-CLI, the same thing is one command:

wp maintenance-mode status
wp maintenance-mode deactivate

Then load the site in a private window. If it is back, read the section on half-finished updates below before you move on, because the file was the symptom.

Why WordPress goes into maintenance mode at all

When you update core, a plugin or a theme, the upgrader writes a one-line PHP file to the site root. We read the source in wp-admin/includes/class-wp-upgrader.php (WordPress trunk, fetched 2026-09-18). The maintenance_mode() method builds the file like this:

$maintenance_string = '<?php $upgrading = ' . time() . '; ?>';

The result is a file such as <?php $upgrading = 1789000000; ?>: a fixed Unix timestamp of when the update started. When the update finishes, the same method deletes the file.

If the update is interrupted (the browser tab closes, PHP hits max_execution_time, the server runs out of memory, the disk fills), the delete never runs. The file stays behind.

The ten-minute rule nobody mentions

On every request, WordPress calls wp_is_maintenance_mode() in wp-includes/load.php. It includes the file and then does this:

// If the $upgrading timestamp is older than 10 minutes, consider maintenance over.
if ( ( time() - $upgrading ) >= 10 * MINUTE_IN_SECONDS ) {
	return false;
}

So a .maintenance file left by core stops working by itself ten minutes after the update began. The file is still on disk, but WordPress ignores it.

That changes the diagnosis. If you are still seeing the maintenance screen well past ten minutes, one of four things is true.

Stuck for longer than ten minutes: the four real causes

1. A cache is serving the old 503

When maintenance mode is active, core sends an HTTP 503 with a Retry-After: 600 header. Most caches refuse to store a 503, but not all of them, and a misconfigured CDN or reverse proxy can keep serving that page after the origin has recovered.

Test the origin directly and compare it with what the public sees:

curl -sI https://example.com/ | head -5
curl -sI "https://example.com/?nocache=$(date +%s)" | head -5

If the cache-busted request returns 200 and the plain one returns 503, purge the page cache and the CDN. Nothing on the server needs to change.

2. The file was not written by core

Core writes a literal number. A file written by a deploy script, a tutorial or a plugin sometimes looks like this instead:

<?php $upgrading = time(); ?>

That value is always "now", so the ten-minute check never passes and the site stays in maintenance mode until someone deletes the file.

Open the file before you delete it. If it contains time(), find out what wrote it, or it will come back on the next deploy.

3. A maintenance plugin or a drop-in is showing the page

Plugins such as coming-soon and maintenance-mode tools render their own page and have nothing to do with .maintenance.

Deleting the file does nothing. Deactivate the plugin instead:

wp plugin list --status=active
wp plugin deactivate <slug>

There is also a drop-in. If wp-content/maintenance.php exists, wp_maintenance() loads it instead of the default message.

It only runs while maintenance mode is active, but a custom one can make a normal error look like maintenance. Check for it with ls wp-content/maintenance.php.

4. It is not maintenance mode at all

A fatal error after a failed update can show "There has been a critical error on this website", which people often report as stuck. Turn on WP_DEBUG_LOG and read wp-content/debug.log rather than guessing.

The damage the guides skip: a half-finished update

Deleting .maintenance makes the site load. It does not finish the update that was interrupted, and that is the part that actually breaks things a day later.

A plugin update replaces the whole plugin folder. If it stopped partway, you can end up with a folder that is missing files, and a fatal error the first time that code runs.

Or the folder is gone, and WordPress deactivates the plugin because its main file no longer exists.

Check what state you are in:

wp core verify-checksums
wp plugin verify-checksums --all
wp plugin list
ls wp-content/upgrade wp-content/upgrade-temp-backup 2>/dev/null

verify-checksums compares files against the wordpress.org originals, so it catches a half-copied core or plugin. It only works for plugins hosted on wordpress.org.

Since WordPress 6.3, the upgrader moves the old plugin or theme into wp-content/upgrade-temp-backup/ before installing the new one, and moves it back if the update fails. If you find a folder there after a crash, it is the previous working version.

That is your fallback if a plugin folder is broken.

The cleanest repair is to reinstall the exact version:

wp plugin install <slug> --version=<x.y.z> --force

For core, wp core download --force --skip-content replaces core files without touching wp-content.

"Another update is currently in progress"

This message is a different lock, and deleting .maintenance does not clear it. Core updates take a lock stored as the core_updater.lock option, created with a 15-minute timeout in class-core-upgrader.php.

Wait fifteen minutes and it releases by itself. If you cannot wait, delete it:

wp option delete core_updater.lock

Only do that when you are sure no update is really running, for example because the PHP process that started it has died.

How to stop it happening again

Updating twenty plugins in one click is one long request, and long requests are the ones that get cut off. Update in small groups, and update from WP-CLI where you can, since the command line is not subject to the web server's request timeout.

The more useful habit is to run the update somewhere that does not matter first. If an update times out or fatals, you want that to happen on a copy.

That is what we built SandyWP for.

Clone the live site into a disposable sandbox with the Cloner plugin, run the same updates there, and read the result in Debug mode.

You get SSH and SFTP access, so every WP-CLI command in this post works as written. When an update goes wrong in the sandbox, you throw the sandbox away.

A sandbox is not the answer for everything. If a live site is stuck right now, fix the live site with the steps above first.

And if you need to push tested changes back to production, that is a staging workflow, not a sandbox one. The upgrade testing page covers where the line falls.

Quick reference

  • Stuck under ten minutes: an update is probably still running. Wait.
  • Stuck over ten minutes: check the cache first, then open .maintenance and look for time().
  • Deleting the file did nothing: a maintenance plugin, a cache, or a fatal error.
  • Site is back: run verify-checksums before you call it fixed.
  • "Another update is in progress": wait 15 minutes, or delete the core_updater.lock option.