← Back to blog

WordPress auto-update plugins: how to turn them on, and what the rollback actually checks

The SandyWP team 8 min read

To auto-update a WordPress plugin, go to Plugins > Installed Plugins and click Enable auto-updates in that plugin's row, or select several plugins and use the bulk action. From the command line, wp plugin auto-updates enable <plugin> does the same thing, and --all covers every plugin.

The part worth understanding is what happens after you click.

Since WordPress 6.6, a plugin auto-update that causes a fatal error is rolled back automatically. The check behind that rollback is narrower than most people assume.

It loads your home page once, logged out, and only for plugins that were already active. If the update breaks checkout, the admin, a REST endpoint or a cron job, the update stays.

We read the pages ranking for this phrase before writing. The WordPress.org documentation and the WPBeginner guide both explain the toggle and recommend a backup, and neither mentions the rollback at all.

The one other guide we read that does mention it says it "catches PHP fatal errors", which is true and leaves out where it looks for them.

How to enable auto-updates for plugins

There are three ways, and they all write to the same place: the auto_update_plugins site option, a list of plugin files.

In the admin. The Plugins screen has an "Automatic updates" column with an Enable auto-updates link per plugin. The bulk actions menu has Enable Auto-updates and Disable Auto-updates for several at once.

With WP-CLI (available since WP-CLI 2.5.0, per the command docs):

# one plugin
wp plugin auto-updates enable akismet

# every installed plugin
wp plugin auto-updates enable --all

# which plugins currently have it on
wp plugin auto-updates status --all --enabled-only --field=name

With a filter. add_filter( 'auto_update_plugin', '__return_true' ); in a must-use plugin forces auto-updates on for everything. It runs after the per-plugin option, so it overrides whatever the admin toggles say, and the Plugins screen does not reflect it.

That last point is why we prefer the option over the filter. The option is visible to whoever manages the site next; the filter is invisible until someone greps for it.

If you are trying to do the opposite, disabling automatic updates covers the precedence order in detail.

When do plugin auto-updates actually run?

Twice a day, on WP-Cron. Core schedules wp_version_check as a twicedaily event, and when that check runs from cron it fires wp_maybe_auto_update, which runs the updater (wp-includes/update.php, wordpress-develop 7.1 branch).

If WP-Cron is not running, auto-updates do not run either, and the toggle still says "enabled". Our post on wp-cron.php covers how to tell whether it is.

On multisite, the updater returns early unless it is running on the main site of the main network. The auto-update you enabled happens network-wide, but only that one site's requests can trigger it.

What happens during a plugin auto-update

Reading WP_Automatic_Updater::update() in wp-admin/includes/class-wp-automatic-updater.php, each plugin goes through the same sequence:

  1. Eligibility. The offer must not be flagged disable_autoupdate, the auto_update_plugin filter must not say no, and the server's PHP must meet the new version's requires_php. A plugin that needs newer PHP is skipped silently.
  2. Maintenance mode on. The .maintenance file is written, so visitors get the "Briefly unavailable for scheduled maintenance" page.
  3. The update. The old version is kept as a temporary backup.
  4. Maintenance mode on again, because the upgrader switched it off when it finished.
  5. If the plugin was active before the update: wait two seconds, then make a loopback request to check for a fatal error.
  6. If a fatal error is found: restore the backup and record the failure for the admin email.
  7. Maintenance mode off.

Plugins are processed one at a time, so a site with ten pending updates goes in and out of maintenance mode ten times. Each check can hold it there for up to 50 seconds, which is the loopback's timeout.

What the rollback checks, exactly

The fatal-error check is has_fatal_error(), added in 6.6. It requests home_url( '/' ) with a scrape key in the query string.

That key tells WordPress to skip maintenance mode for this one request and to print error_get_last() at shutdown if it was a fatal error type. It is the same mechanism the plugin and theme file editors have used since 4.9 to refuse an edit that breaks the site.

Three details decide what it can see.

It is one URL. The request goes to the home page and nowhere else. Code that only runs on /checkout/, on a single product page, in wp-admin, under /wp-json/ or in a cron callback is never executed by the check.

It is usually logged out. The request forwards the cookies of the current PHP request.

When the updater runs from WP-Cron there is normally no user session behind it, so the home page is loaded as an anonymous visitor. Anything that only breaks for logged-in users or administrators passes.

It only runs for plugins that were active. If the plugin was inactive before the update, the check is skipped, and with WP_DEBUG_LOG on, core logs that "it is inactive and will not be checked for fatal errors". An inactive plugin can update itself into a broken state, and you find out the day you activate it.

And it only catches fatals.

The scrape looks for E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR and E_RECOVERABLE_ERROR. A warning, a deprecation, a JavaScript error, a broken layout or a form that silently stops submitting all count as success.

Why plugin auto-updates sometimes roll back for no reason

If the loopback request itself fails, core treats that as a fatal error and rolls back. The merge proposal says so directly: "Rollback Auto-Update will consider it as a PHP fatal error detected."

So a server that cannot reach its own home page will roll back every auto-update of an active plugin. Typical causes are a firewall blocking requests from the server to itself, HTTP basic auth on the site, or DNS that resolves the domain to an address the server cannot reach.

Core does forward basic auth credentials if the current request has them, but a cron run triggered by a visitor or a system crontab usually does not. The proposal also notes that the next scheduled check tries the same update again, so this repeats twice a day.

Site Health has a loopback test under Tools > Site Health. If it fails, auto-updates for active plugins will not stick.

There is an opposite case too. If something other than WordPress answers the loopback (a full-page cache or a CDN serving the home page from cache), the response contains no scrape marker, and the check reports no fatal error even when there is one.

Should you turn on auto-updates for every plugin?

For small plugins that do one thing and break visibly when they break, auto-updates are usually the right call. You get security fixes the same day, and the rollback covers the crash case.

For plugins whose failures happen away from the home page, the rollback is no protection at all. That means WooCommerce and its payment gateways, membership and LMS plugins, form plugins, page builders, and anything that runs in cron or on the REST API.

A checkout that stops taking payments does not produce a fatal on the home page. Those plugins should update on a schedule you choose, after you have seen the new version work.

How to see what the auto-updater did

Turn on WP_DEBUG and WP_DEBUG_LOG. When both are set, the updater writes its own trace to debug.log: "Automatic plugin updates starting...", each plugin it upgraded, "Scraping home page...", and either "has no fatal errors" or the rollback message.

Without those two constants, the only record is the email to the admin address. Core sends one after each run for success, failure, or a mix, unless the auto_plugin_update_send_email filter turns it off.

On a sandbox, Debug mode switches both constants on from the dashboard and shows debug.log beside the toggles, so you can read the trace without editing wp-config.php.

Test an auto-update before it happens on production

The useful test is the auto-update path itself, on a copy of the real site, with the same plugins active. A clean install proves very little, because the fatal usually comes from the combination.

  1. Copy the site. The SandyWP Cloner builds the copy from your live site without modifying it. The database comes with it, including the auto_update_plugins option, so the copy has the same plugins enabled for auto-updates.
  2. Turn on debug logging so the updater's trace lands in debug.log.
  3. Put the plugin back on its current production version if the copy is already newer. wp plugin install <slug> --version=<x.y.z> --force does it for anything on WordPress.org.
  4. Run the updater the way cron would. Over SSH, with WP-CLI preinstalled, wp eval 'wp_maybe_auto_update();' calls the same function the cron hook calls.
  5. Read the log, then click the pages the check skipped. The log tells you whether the home page fataled. You still have to open checkout, the admin screens and the forms yourself, because the updater never will.

If the loopback fails on your copy, that is worth knowing too: the log line will say "Loopback request failed", which is the false-rollback case above.

This is the same routine as testing a plugin update by hand, with one extra step: it exercises the rollback logic production will run. For a whole-site version of this workflow, see upgrade testing.

What a sandbox will not tell you

A copy has no real traffic, and its cron runs when you tell it to. If the failure you are worried about is a race between an update and a burst of visitors, or a slow update on a large plugin directory under load, a sandbox will not reproduce it.

It also will not tell you whether your production server can reach itself. Run the Site Health loopback test on production for that.

The sandbox answers a different question: whether the new version of this plugin, next to your other plugins and your data, works on the pages the rollback never looks at.