
A plain text file called .maintenance, written to your WordPress root the moment any core, plugin, or theme update starts, is the entire mechanism behind the maintenance screen. Before touching a single file, WordPress's upgrader class calls maintenance_mode(true), which writes a one-line PHP file containing a variable assignment: {$upgrading = time();}. On every request, wp_maintenance() in wp-includes/load.php checks whether that file exists, and if it does, shows visitors "Briefly unavailable for scheduled maintenance. Check back in a minute." instead of loading the site. When the update finishes normally, the same upgrader calls maintenance_mode(false), which deletes the file, and the site returns to normal within the same request cycle.
WordPress core actually only trusts that file for ten minutes — the timestamp written inside it is compared against the current time, and if it is older than 10 * MINUTE_IN_SECONDS, wp_maintenance() gives up and lets the request through anyway. In practice, sites still get stuck far longer than ten minutes, for two reasons covered below: something keeps rewriting the file with a fresh timestamp, or a caching layer in front of PHP keeps serving the maintenance response long after WordPress itself has stopped enforcing it.
The file only gets deleted if the PHP process that wrote it lives long enough to finish the update and reach the cleanup call — anything that kills the process first leaves it behind permanently. The most common triggers are a PHP execution timeout during a large core upgrade or a plugin with a heavy database migration, the server running out of memory while extracting a big theme or plugin zip, a network interruption that drops the connection mid-upload while a large file transfers over a slow link, or two update processes racing each other — an auto-update cron job firing while you are also updating manually through wp-admin, both writing and deleting the same file out of order.
Retry logic makes this worse rather than better. Some plugins and management tools detect that an update did not complete and automatically re-trigger it, which rewrites .maintenance with a new $upgrading timestamp on every attempt, resetting the ten-minute window each time it fires and making the site look stuck indefinitely even though no individual attempt is actually still running. A CDN or a page-cache plugin that cached the maintenance response before the file was deleted can produce the same symptom for a different reason — the origin has recovered, but visitors are still being served a stale cached copy of the maintenance page.
Before deleting anything, rule out that the update is genuinely still in progress on the server — deleting the file mid-write is safe, but interrupting a real in-flight database migration by killing its PHP process is not. Check for a hung or long-running PHP-FPM worker tied to the site before you touch the file.
Wait at least a few minutes and re-check ps aux before deleting the file. If a php process for the site is still consuming CPU and its runtime keeps climbing, the update may genuinely be mid-write on a large migration — give it more time or check the site's PHP error log for a fatal timeout first. Only delete .maintenance once you can confirm no matching process is active.
Once you have confirmed nothing is still writing to it, deleting the file is the entire fix — WordPress needs nothing else to return to normal. Connect over SFTP or open your host's file manager, navigate to the WordPress root at /home/<username>/apps/<sitename>/, enable "show hidden files" (dotfiles do not appear by default in most file managers and FTP clients), and remove .maintenance.
If you would rather not spin up an FTP client just to remove one hidden file, CloudStick's Advanced File Manager lets you see and delete hidden dotfiles like .maintenance directly from the dashboard — toggle hidden files on in the file browser, select it, and delete it in a couple of clicks, with no separate SFTP session required. Reload the site immediately after the delete; there is no cache to clear on the WordPress side, since the maintenance screen was never a cached page, just a runtime check against a file that no longer exists.
With the site back up, go straight to the Updates screen in wp-admin and re-run whichever core, plugin, or theme update was interrupted — a stuck .maintenance file does not corrupt the update itself, it just blocks the site while the interrupted attempt is cleaned up. If the same update fails again in the same way, the cause is almost always resource-related rather than random: raise memory_limit and max_execution_time in the PHP-FPM pool for that site before trying a large core upgrade, and disable any other auto-update plugin or scheduled task that might be firing an update against the same site at the same time.
For large migrations — a major version jump, a bulk plugin update across a multisite install, or anything you expect to take more than a minute or two — run it from the command line with WP-CLI instead of through the browser, so the process is not tied to a page request that a closed tab or a client-side timeout can kill mid-write. To recap: the .maintenance file is not the problem, it is a leftover marker from an update that never finished; confirm no PHP process is still working on it, delete the file at the WordPress root, and re-run the interrupted update once the resource constraint that killed it the first time is addressed.

