Why Your WordPress Auto-Updates Silently Stopped Working

· 9 min read

Last month I ran a routine audit across the 70+ sites I manage and found three that were still running WordPress 6.9.4 — two minor versions behind, and missing the critical wp2shell patch (CVE-2026-63030) that WordPress.org had force-pushed to every auto-updating install weeks earlier.

Auto-updates were enabled on all three. WP-Cron was firing. No .maintenance file stuck in the root. The sites looked healthy. They just weren't updating.

The culprit was a stale lock row in wp_options — an option called core_updater.lock that WordPress sets before running an automatic update and deletes when it finishes. On these three sites, the update process had crashed mid-flight, the lock was never cleared, and every subsequent auto-update attempt saw it and silently backed off.

No admin notice. No email. No error in the log. Just a site quietly falling behind on security patches while everyone assumed auto-updates had it covered.

How the lock mechanism works

WordPress uses two locks during automatic updates, both stored as regular rows in the wp_options table (not transients, despite what many guides claim):

  • auto_updater.lock — acquired by WP_Automatic_Updater::run() before any auto-update work begins. Prevents overlapping update runs.
  • core_updater.lock — acquired by Core_Upgrader::upgrade() specifically for core updates. Prevents two core updates from running simultaneously.

Both are created by WP_Upgrader::create_lock() in wp-admin/includes/class-wp-upgrader.php. The method runs a raw INSERT IGNORE INTO wp_options query, storing the current Unix timestamp as the value with autoload set to off.

Before acquiring the lock, WordPress checks whether the existing timestamp is within a timeout window. For core_updater.lock, that window is 15 minutes. If the stored timestamp is older than 15 minutes, WordPress considers the lock stale, deletes it, and creates a fresh one.

In theory, this means a stuck lock should self-clear after 15 minutes. In practice, I've seen it fail for two reasons:

  1. The timestamp stored is zero or malformed. A crash at exactly the wrong moment can write a zero value. Zero minus current time is always within the window, so the lock never expires.
  2. WP-Cron itself stops firing reliably. If the site has low traffic and no system cron replacement, the twice-daily wp_maybe_auto_update hook simply doesn't run often enough to retry after the lock window expires.

How to tell if your site is stuck

The admin dashboard shows a notice for some failure modes — "An automated WordPress update has failed to complete — please attempt the update again now" — but only when WordPress sets the auto_core_update_failed site option. A stuck lock doesn't always trigger that. The update never actually fails; it never starts.

Here's how to check from the command line:

wp option get core_updater.lock --path=/var/www/yoursite

If that returns a Unix timestamp, the lock exists. Compare it against the current time:

echo "Lock age: $(( $(date +%s) - $(wp option get core_updater.lock --path=/var/www/yoursite) )) seconds"

If the lock is more than 15 minutes old, it's stale. Also check the auto-updater lock:

wp option get auto_updater.lock --path=/var/www/yoursite

To check via SQL directly (useful if WP-CLI isn't installed):

SELECT option_name, option_value,
       FROM_UNIXTIME(option_value) AS lock_set_at,
       TIMESTAMPDIFF(MINUTE, FROM_UNIXTIME(option_value), NOW()) AS minutes_ago
FROM wp_options
WHERE option_name IN ('core_updater.lock', 'auto_updater.lock');

If minutes_ago is in the hundreds or thousands, you've found your problem.

How to fix it

Delete the stale locks and trigger the update manually:

wp option delete core_updater.lock --path=/var/www/yoursite
wp option delete auto_updater.lock --path=/var/www/yoursite
wp core update --path=/var/www/yoursite

Then verify the update applied:

wp core version --path=/var/www/yoursite

If there's also a .maintenance file stuck in the site root (which causes the separate "Briefly unavailable" error), delete that too:

rm /var/www/yoursite/.maintenance

What causes the lock to get stuck

Every case I've traced back has been one of these:

PHP execution timeout. The core update downloads a 15-25 MB ZIP from downloads.wordpress.org, extracts it to wp-content/upgrade/, then copies files into wp-admin/ and wp-includes/. On shared hosting with a 30-second max_execution_time, that's often not enough — especially if the download itself is slow. PHP kills the process, the lock stays.

Memory exhaustion. ZIP extraction is memory-intensive. If memory_limit is set to 128M or lower and the site is already using a chunk of that for plugins loaded during the update, PHP runs out and crashes.

Firewall or WAF blocking downloads. ModSecurity rules, Cloudflare WAF, or an overzealous open_basedir restriction can silently block requests to api.wordpress.org or downloads.wordpress.org. The download fails, the process exits, the lock remains. Check your server's error log for blocked outbound requests.

Disk space. WordPress needs roughly three times the ZIP size in free space — the download, the extracted copy, and the live files being replaced. On a VPS running multiple sites, I've seen /tmp fill up from accumulated upgrade artefacts that never got cleaned.

Database connection dropped. A brief MariaDB restart or a max_connections limit being hit during the update means WordPress can't delete the lock row. The update itself may have completed, but the lock persists and blocks the next run.

How to prevent it across managed sites

After finding those three stuck sites, I added a monitoring check that runs daily across every site I maintain:

#!/bin/bash
for site in /var/www/*/public_html; do
  lock=$(wp option get core_updater.lock --path="$site" 2>/dev/null)
  if [ -n "$lock" ] && [ "$lock" != "0" ]; then
    age=$(( $(date +%s) - lock ))
    if [ "$age" -gt 900 ]; then
      echo "STALE LOCK: $site (age: ${age}s)"
      wp option delete core_updater.lock --path="$site"
      wp option delete auto_updater.lock --path="$site"
    fi
  fi
done

This catches stale locks before they become a security gap. I run it as a system cron job alongside my WP-Cron replacement scripts.

Beyond lock monitoring, a few things reduce the chance of locks getting stuck in the first place:

Increase PHP limits for CLI context. Auto-updates run via WP-Cron (which runs in the web PHP context) unless you've switched to system cron. Either way, ensure max_execution_time is at least 120 seconds and memory_limit is 256M or higher:

; /etc/php/8.3/cli/conf.d/99-wordpress.ini
max_execution_time = 300
memory_limit = 512M

Replace WP-Cron with system cron. A system cron job calling wp cron event run --due-now every five minutes is far more reliable than waiting for site traffic to trigger wp-cron.php. I've written about this in detail.

Clean up the upgrade directory. Stale extraction folders in wp-content/upgrade/ consume disk space and occasionally confuse the updater. A weekly cron to clear anything older than 24 hours keeps it tidy:

find /var/www/*/public_html/wp-content/upgrade/ -mindepth 1 -mtime +1 -exec rm -rf {} +

Check update status, not just uptime. Uptime monitoring tells you the site is responding. It doesn't tell you the site is three versions behind on security patches. I check wp core check-update --format=json across all sites weekly and flag any that aren't on the latest minor release.

The real risk: silent exposure

A site with a stuck auto-update lock doesn't go down. It doesn't show errors. It doesn't email anyone. It just sits there, running an increasingly outdated version of WordPress, while the admin dashboard shows a green "Your site is up to date" message from the last successful check.

When WordPress.org force-pushed the wp2shell patch in July, every site with a working auto-updater received it within hours. The three sites I found with stuck locks? They would have sat exposed to a pre-authentication remote code execution vulnerability indefinitely — until someone logged in and manually clicked "Update Now," or until an attacker found them first.

Auto-updates are not a set-and-forget solution. They're a mechanism that can fail silently, and when they do, the site is worse off than one where updates are managed manually — because at least with manual updates, someone is paying attention.


Stop Firefighting. Start Maintaining.

I manage 70+ WordPress sites for agencies and businesses. Whether you need ongoing maintenance, emergency support, or a one-off performance fix — I can help.

View Maintenance Plans Get in Touch

Stop Firefighting. Start Maintaining.

I manage 70+ WordPress sites for agencies and businesses. Whether you need ongoing maintenance, emergency support, or a one-off performance fix — I can help.

View Maintenance Plans Get in Touch

Get in Touch to Discuss Your Needs