Updates and Auto-Updates

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

This sub-section’s WordPress bibliography lists the reference material consulted while preparing these pages.

This page was written against WordPress 7.1.3 (released 2026-10-06; 7.1.4, a security release of 2026-10-28, is the version to run in production) and WP-CLI 2.12.0, and checked on 2026-10-28. WordPress 7.1 requires PHP 7.4 at minimum, and PHP 8.3 or newer is recommended. WordPress 7.2 is in beta and due in December 2026; nothing in its betas changes the update mechanisms described here. Every command and wp eval-file probe below was run against a WordPress site in Docker (MariaDB 11.8, the wordpress:cli-2.12.0-php8.4 image, and wordpress:7.1.3-php8.4-apache where a web server was needed), first installed at 7.1.2 so that real updates to 7.1.3 were available. Every output block is real output, shortened only where marked.

Updates are the most effective security measure a WordPress site has: most compromised sites run an outdated plugin, theme or core version. This page is for administrators who decide how a site is updated: what WordPress updates on its own, how to change that with constants and filters, the emails it sends, the 6.6 rollback, and how Lumen Press updates staging and production. Version numbers (X.Y is a major release, X.Y.Z a minor one), the release cadence and beta builds are in Versions and Release Cycle; the per-plugin and per-theme Enable auto-updates toggles, wp plugin auto-updates and the 6.3 rollback of failed manual updates are in Choosing and Managing Plugins and Themes.

What gets updated, and by whom

WordPress distinguishes five kinds of update. Each has its own default and its own switch.

Kind Default since Who decides Main switch

Minor core release (7.1.2 to 7.1.3): maintenance and security fixes

Automatic since 3.7

WordPress, unless the site opts out

WP_AUTO_UPDATE_CORE, allow_minor_auto_core_updates

Major core release (7.1 to 7.2)

Automatic for sites installed on 5.6 or later; existing sites were left unchanged by the 5.6 upgrade and can opt in

The administrator on Dashboard > Updates, or code

auto_update_core_major option, WP_AUTO_UPDATE_CORE, allow_major_auto_core_updates

Development core build (beta, RC, nightly), only on sites already running one

Automatic since 3.7

WordPress

allow_dev_auto_core_updates

Plugins and themes

Manual, unless the administrator enables the toggle (5.5+) or WordPress.org forces a security release

The administrator, per item; the WordPress.org security team for forced releases

auto_update_plugin, auto_update_theme

Translations (language packs for core, plugins and themes)

Automatic since 3.7

WordPress

auto_update_translation

Security releases are minor releases, so this default applies them without anyone acting: WordPress 7.1.4, a security-only release published on 2026-10-28 (it fixes a remote code execution weakness in the upgrade process and a series of stored XSS issues), was offered to every 7.1 site as a minor update, and the announcement notes that sites with automatic background updates would start updating automatically; on a site that opted out, update to it immediately. Source: WordPress News: WordPress 7.1.4

The 5.6 rule comes from three site options. A fresh install writes auto_update_core_dev, auto_update_core_minor and auto_update_core_major, all enabled; the database upgrade to 5.6 wrote auto_update_core_major as unset on existing sites, which counts as "off". On the test site, freshly installed at 7.1.2:

wp option list --search='auto_update*'
wp core check-update
option_name	option_value
auto_update_core_dev	enabled
auto_update_core_major	enabled
auto_update_core_minor	enabled
version	update_type	package_url
7.1.3	minor	https://downloads.wordpress.org/release/wordpress-7.1.3-partial-2.zip

The package is a partial build: only the files that changed between 7.1.2 and 7.1.3. Core falls back to the full package when the partial one cannot be used.

The Dashboard > Updates screen

Dashboard > Updates (wp-admin/update-core.php) handles every kind of update. Under "Last checked on …​ at …​" and a Check again link, it has four sections. WordPress offers Update to version 7.1.3, or Re-install version 7.1.3 (a repair for modified or corrupt core files), and a status line with the core policy: "This site is automatically kept up to date with each new version of WordPress." or "…​with maintenance and security releases of WordPress only." A link switches between the two by writing auto_update_core_major as enabled or disabled. Plugins, Themes and Translations list pending updates with Update Plugins, Update Themes and Update Translations buttons.

The policy link disappears when the policy is decided in code: when WP_AUTO_UPDATE_CORE is defined, when the whole updater is disabled, or when any code is hooked to allow_major_auto_core_updates. If a version-control checkout is detected, the status line reads "This site appears to be under version control. Automatic updates are disabled."

Only users with the update_core, update_plugins, update_themes and update_languages capabilities see the screen and its notices, which by default means Administrators on a single site and only Super Admins on Multisite, where the screen lives in the Network Admin (see Multisite Networks).

Other routes: an update now link on each row of Plugins and Appearance > Themes; WP-CLI for scripted updates (Updating with WP-CLI); hosting panels such as WP Toolkit in cPanel and Plesk, whose schedules run alongside core’s updater unless the host disables it (find out which one is in charge); and the manual update (replace everything except wp-config.php and wp-content/, then visit /wp-admin/upgrade.php) for servers where nothing else works.

How background updates run

Background updates are driven by WP-Cron. On init, wp_schedule_update_checks() schedules three twice-daily events: wp_version_check, wp_update_plugins and wp_update_themes. When wp_version_check() runs inside a cron request, it fires the wp_maybe_auto_update action, whose callback creates a WP_Automatic_Updater and calls its run() method. A site whose WP-Cron does not run (disabled with DISABLE_WP_CRON and no system cron, or a site without traffic) never updates itself; see WP-Cron and Scheduled Tasks.

run() returns at once if the updater is disabled, if the site is not the main site of the main network, or if another run holds the auto_updater.lock option (it expires after one hour). Otherwise it updates eligible plugins, then themes, then core (find_core_auto_update(), under its own 15-minute core_updater.lock), then translations, and finally sends the emails and fires automatic_updates_complete with every result.

Before each plugin, theme or core update, the site is put in maintenance mode: a .maintenance file in the WordPress root makes every front-end and admin request answer "Briefly unavailable for scheduled maintenance. Check back in a minute." The file is deleted at the end; if a run dies half-way the file stays, but WordPress ignores it once it is more than ten minutes old.

The decision chain for one update

For each offer, WP_Automatic_Updater::should_update() decides whether to install it. The tree below follows the code of WordPress 7.1.3; the filters are where a site’s own policy enters.

flowchart TD A["WP-Cron runs wp_version_check (twice daily)"] --> B{"DISALLOW_FILE_MODS, AUTOMATIC_UPDATER_DISABLED or automatic_updater_disabled filter?"} B -- yes --> X["No background updates and no update emails"] B -- no --> C{"Main site of the main network and auto_updater lock free?"} C -- no --> X2["Skip this run"] C -- yes --> D{"Files writable without FTP credentials and no VCS checkout?"} D -- no --> E{"Core offer?"} E -- yes --> M["Email: WordPress x.y.z is available, if WordPress.org flags the offer"] E -- no --> S["Skip this item"] D -- yes --> T{"Type of update"} T -- core --> K["Minor, major or development? Site options, WP_AUTO_UPDATE_CORE, allow_minor/major/dev_auto_core_updates"] T -- "plugin or theme" --> P["Forced by WordPress.org, or toggle on in auto_update_plugins / auto_update_themes"] T -- translation --> L["Offer marked autoupdate by WordPress.org"] K --> F{"auto_update_core filter"} P --> G{"auto_update_plugin / auto_update_theme filter"} L --> H{"auto_update_translation filter"} F -- false --> M F -- true --> R{"Server PHP and MySQL meet the release?"} G -- false --> S G -- true --> Q{"Server PHP meets Requires PHP?"} H -- false --> S H -- true --> U["Install in maintenance mode"] R -- yes --> U R -- no --> S Q -- yes --> U Q -- no --> S U --> V{"Plugin that was active?"} V -- no --> Z["Keep the new version"] V -- yes --> W{"Loopback to the home page fails or shows a fatal error?"} W -- no --> Z W -- yes --> Y["Restore the previous version (6.6+)"]

Three details of the chain are easy to miss:

  • The auto_update_{$type} filters run after the built-in decision and receive it as $update, so a filter can both grant and refuse. Returning false from auto_update_plugin also refuses the security releases that WordPress.org forces.

  • For core, the built-in decision is Core_Upgrader::should_update_to_version(). It compares the branch (the first two numbers) of the running and offered versions: same branch is a minor update, a newer branch is a major one, and a running version with a hyphen (7.2-beta2) is a development version. It also refuses an offer after a critical earlier failure (see Update emails).

  • The new_files flag of a core offer relaxes the file-ownership check for partial builds that add no new files.

Constants in wp-config.php

Constant Values Effect

WP_AUTO_UPDATE_CORE

true, false, 'minor'

true allows development, minor and major updates; false refuses all three; 'minor' allows only minor updates. The code also accepts 'beta', 'rc', 'development' and 'branch-development': inside should_update_to_version() they act like true, and wp_version_check() also sends the value to api.wordpress.org as the update channel, so the site can be offered that channel’s pre-release builds. When defined, the constant overrides the three site options and hides the policy link on Dashboard > Updates. Filters still override it.

AUTOMATIC_UPDATER_DISABLED

true

Turns the background updater off entirely: no core security releases, no translations, no plugin or theme updates, and no update emails. The handbook strongly discourages it.

DISALLOW_FILE_MODS

true

Refuses every file change made by WordPress: installing, uploading, updating and deleting plugins and themes, the file editors, and the background updater (wp_is_file_mod_allowed( 'automatic_updater' ) is false). Meant for sites deployed from version control or images, where updates arrive by deployment.

FS_METHOD

direct, ssh2, ftpext, ftpsockets

How WordPress writes files. Background updates only run when no credentials are needed, which normally means the PHP user owns the files (direct). See File Permissions and Filesystem.

CORE_UPGRADE_SKIP_NEW_BUNDLED

true

A major update does not install newly bundled default themes and plugins

Lumen Press production defines DISALLOW_FILE_MODS (see Security Hardening): its code is owned by a deploy user, PHP writes only uploads, and every update, security releases included, reaches it through the deployment pipeline after staging has taken it. The background updater therefore runs on staging, where WordPress updates itself in place, and on any classically maintained site. The staging configuration keeps only the constants that must hold even if a must-use plugin is missing:

<?php
// wp-config.php (Lumen Press staging), above the "That's all, stop editing!" line.
define( 'WP_ENVIRONMENT_TYPE', 'staging' );
define( 'WP_AUTO_UPDATE_CORE', 'minor' ); // Minor releases even without the policy plugin, which adds major ones.
define( 'FS_METHOD', 'direct' );          // On staging the PHP-FPM user owns the WordPress files.

Filters in a must-use plugin

Constants are coarse; filters can decide per environment, per item and per day. They belong in a must-use plugin in wp-content/mu-plugins/, never in wp-config.php: WordPress is not fully loaded while wp-config.php runs, so add_filter() there is fragile and the handbook notes it conflicts with WP-CLI. A must-use plugin cannot be deactivated from the dashboard, which is what an update policy needs. Must-use plugins themselves are explained in Choosing and Managing Plugins and Themes.

Hook Use

automatic_updater_disabled

Return true to disable the whole updater, like the constant

auto_update_{$type}: auto_update_core, auto_update_plugin, auto_update_theme, auto_update_translation

Final yes or no per offer; receives the built-in decision and the offer object ($item->slug, $item->plugin, $item->theme, $item->language, $item->current)

allow_dev_auto_core_updates, allow_minor_auto_core_updates, allow_major_auto_core_updates

Simpler core switches applied inside should_update_to_version(), after the constant and the options

automatic_updates_is_vcs_checkout

Override version-control detection (see Version-control checkouts)

auto_core_update_send_email and the other email filters in Update emails; auto_core_update_email, auto_plugin_theme_update_email, automatic_updates_debug_email

Turn each kind of update email off; change its recipients, subject, body or headers

pre_auto_update (action)

Fires just before each automatic update, with the type, offer and filesystem context

automatic_updates_complete (action)

Fires after a run with every attempted update and its result

The Lumen Press policy lives in wp-content/mu-plugins/lumen-update-policy.php on every site the updater maintains (on production DISALLOW_FILE_MODS makes it inert). It complements the site’s plugin policy (the "always update Two Factor and Akismet, never auto-update themes" filters shown on the plugins page): minor core releases everywhere, major releases automatically only on staging, translations always, no plugin or theme updates at weekends when nobody is on call, no routine "success" emails, and a log line for every run.

<?php
/**
 * Plugin Name: Lumen Update Policy
 * Description: Background update policy for Lumen Press: minor core releases everywhere, major releases only on staging, translations always, no plugin or theme updates at weekends, quieter emails.
 * Version:     0.1.0
 * Author:      Lumen Press
 * License:     GPL-2.0-or-later
 */

defined( 'ABSPATH' ) || exit;

// Major core releases: staging takes them automatically, production gets them by deployment once tested.
add_filter(
	'allow_major_auto_core_updates',
	static function () {
		return 'staging' === wp_get_environment_type();
	}
);

// Minor (maintenance and security) core releases: always.
add_filter( 'allow_minor_auto_core_updates', '__return_true' );

// Translations: always.
add_filter( 'auto_update_translation', '__return_true' );

// Plugins and themes: no automatic updates at weekends, when nobody is on call.
// Priority 20 runs after filters that force updates on (such as the site policy plugin).
$lumen_weekdays_only = static function ( $update ) {
	return (int) wp_date( 'N' ) >= 6 ? false : $update;
};
add_filter( 'auto_update_plugin', $lumen_weekdays_only, 20 );
add_filter( 'auto_update_theme', $lumen_weekdays_only, 20 );

// Core emails: keep failures ("fail", "critical"), drop the routine "success" message.
add_filter(
	'auto_core_update_send_email',
	static function ( $send, $type ) {
		return 'success' === $type ? false : $send;
	},
	10,
	2
);

// Leave a trace of every background run in the PHP error log.
add_action(
	'automatic_updates_complete',
	static function ( $results ) {
		foreach ( $results as $type => $items ) {
			foreach ( $items as $item ) {
				$outcome = is_wp_error( $item->result ) ? $item->result->get_error_code() : 'ok';
				error_log( sprintf( '[lumen-updates] %s %s: %s', $type, $item->name, $outcome ) );
			}
		}
	}
);

The weekend rule also holds back forced security releases of plugins until Monday; checks run twice a day, so the delay is at most about two days. A site that cannot accept that delay should drop the rule.

Verifying the policy against WP_Automatic_Updater

Reading filters is not proof of what the updater will do. This probe asks WP_Automatic_Updater itself, through should_update(), about four hypothetical offers: a minor core release, a major one, a plugin and a translation. Nothing is downloaded or installed. Keep it outside the web root and run it with wp eval-file.

<?php
/*
 * Asks WP_Automatic_Updater what it would do with a few hypothetical update offers.
 * Nothing is downloaded or installed. Run with: wp eval-file update-probe.php
 */
require_once ABSPATH . 'wp-admin/includes/admin.php';
require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';

$updater = new WP_Automatic_Updater();
$answer  = static function ( $value ) {
	return $value ? 'yes' : 'no';
};

$core_offer = static function ( $version ) {
	return (object) array(
		'current'       => $version,
		'version'       => $version,
		'php_version'   => '7.4',
		'mysql_version' => '5.5.5',
		'new_files'     => true,
	);
};

$offers = array(
	array( 'core', $core_offer( '7.1.4' ), ABSPATH ),
	array( 'core', $core_offer( '7.2' ), ABSPATH ),
	array( 'plugin', (object) array( 'slug' => 'two-factor', 'plugin' => 'two-factor/two-factor.php' ), WP_PLUGIN_DIR ),
	array( 'translation', (object) array( 'type' => 'core', 'language' => 'es_ES', 'autoupdate' => true ), WP_CONTENT_DIR ),
);

printf( "%-22s %s\n", 'environment', wp_get_environment_type() );
printf( "%-22s %s\n", 'running version', wp_get_wp_version() );
printf( "%-22s %s\n", 'updater disabled', $answer( $updater->is_disabled() ) );
printf( "%-22s %s\n", 'VCS checkout', $answer( $updater->is_vcs_checkout( ABSPATH ) ) );

foreach ( $offers as list( $type, $item, $context ) ) {
	$label = 'core' === $type ? "core {$item->current}" : "{$type} " . ( $item->slug ?? $item->language );
	printf( "%-22s %s\n", $label, $answer( $updater->should_update( $type, $item, $context ) ) );
}

On the 7.1.2 test site, freshly installed and without any policy:

environment            production
running version        7.1.2
updater disabled       no
VCS checkout           no
core 7.1.4             yes
core 7.2               yes
plugin two-factor      no
translation es_ES      yes

Changing one thing at a time gave the results below. The third row shows that filters win over the constant: allow_minor_auto_core_updates returning true re-enables minor updates although WP_AUTO_UPDATE_CORE is false.

Configuration core 7.1.4 core 7.2 plugin translation

WP_AUTO_UPDATE_CORE false, no policy plugin

no

no

no

yes

Lumen policy, WP_AUTO_UPDATE_CORE false, WP_ENVIRONMENT_TYPE unset (production)

yes

no

no

yes

Lumen policy, WP_ENVIRONMENT_TYPE staging

yes

yes

no

yes

WP_AUTO_UPDATE_CORE 'minor', no policy plugin

yes

no

no

yes

AUTOMATIC_UPDATER_DISABLED true (policy plugin active)

no

no

no

no

DISALLOW_FILE_MODS true (policy plugin active)

no

no

no

no

Lumen policy, Two Factor toggled on (auto_update_plugins option), on a Wednesday

yes

no

yes

yes

Same, with wp_date( 'N' ) filtered to return 6 (a Saturday)

yes

no

no

yes

Lumen policy, toggle on, .git directory in wp-content/ only

yes

no

no

no

Same, .git directory in the WordPress root

no

no

no

no

Same, plus the VCS override plugin shown in Version-control checkouts

yes

no

yes

yes

AUTOMATIC_UPDATER_DISABLED and DISALLOW_FILE_MODS beat every filter that grants an update, because is_disabled() is checked first; only the automatic_updater_disabled filter can override the former, and only the file_mod_allowed filter the latter.

Version-control checkouts

An updater that rewrites files would fight a deployment that also writes them. So is_vcs_checkout() looks for a .svn, .git, .hg or .bzr directory in the update’s context directory and in every parent up to the filesystem root, and always also in ABSPATH and its parents. When one is found the update is skipped; for a core offer, WordPress falls back to sending the "WordPress x.y.z is available" email (if WordPress.org flags the release). Directories outside PHP’s open_basedir are not checked (since 6.2).

The context differs by type: ABSPATH for core, WP_PLUGIN_DIR for plugins, the theme root for themes and WP_CONTENT_DIR for translations. The last three rows of the probe table above show the effect: a repository that versions only wp-content (the usual layout, see Deployment and CI/CD) blocks plugin, theme and translation updates but not core security releases. A .git anywhere above the WordPress root, such as a whole server directory under version control, blocks everything. Site Health reports it under Background updates as "The folder …​ was detected as being under version control (.git)."

If the checkout is deliberate but WordPress may still update (a server where a configuration repository happens to sit above the site, for example), return false from automatic_updates_is_vcs_checkout, at priority 1 as the handbook recommends, so that later callbacks can still say true for specific paths:

<?php
/**
 * Plugin Name: Lumen VCS Override
 * Description: The release tag is checked out with Git, but WordPress may still apply security releases.
 */
defined( 'ABSPATH' ) || exit;
add_filter( 'automatic_updates_is_vcs_checkout', '__return_false', 1 );

Lumen Press does not need the override: its production servers receive releases by rsync of a build without .git and define DISALLOW_FILE_MODS, and on staging its update policy alone decides.

Update emails

All update emails go to the address in the admin_email option (Settings > General, "Administration Email Address"), not to every administrator. They are sent with wp_mail(), so a site that cannot send mail (see Email Delivery) silently loses them.

Email Sent when Filter to turn it off

"[Site] Your site has updated to WordPress 7.1.3"

A background core update succeeded

auto_core_update_send_email ($type success)

"[Site] WordPress 7.1.3 is available. Please update!"

A core update failed early (fail), or could not be attempted at all (manual: VCS checkout, credentials needed, policy says no) and the WordPress.org offer has the notify flag. Sent once per version and address

auto_core_update_send_email (fail); send_core_update_notification_email (manual)

"[Site] URGENT: Your site may be down due to a failed update"

A core update failed after files started to be copied (critical)

auto_core_update_send_email (critical); not advisable

"[Site] Some plugins were automatically updated", "…​have failed to update", "Some plugins and themes have automatically updated" and similar

After any plugin or theme auto-update, one summary per run

auto_plugin_update_send_email, auto_theme_update_send_email

Debug email with the full log of the run

After every run on a development version (a hyphen in the version, such as 7.2-beta2)

automatic_updates_send_debug_email

The success email from a real background update of the test site from 7.1.2 to 7.1.3 (captured with a pre_wp_mail filter in a test-only must-use plugin, without the Lumen policy that suppresses it):

TO: admin@lumenpress.test
SUBJECT: [Lumen Press] Your site has updated to WordPress 7.1.3
Howdy! Your site at https://lumenpress.test has been updated automatically to WordPress 7.1.3.

No further action is needed on your part. For more on version 7.1.3, see the About WordPress screen:
https://lumenpress.test/wp-admin/about.php
(support forum links and signature)

With the Lumen policy active, the same update sent nothing, left [lumen-updates] core WordPress 7.1.3: ok in wp-content/debug.log, and still recorded "type":"success" with the address and version in the auto_core_update_notified site option, which is how WordPress avoids repeating an email.

Core failures are also remembered, in the auto_core_update_failed site option. A critical failure (disk_full, an error while copying files, or a failed internal rollback) stops all later core auto-updates until an administrator completes a manual update. A non-critical one stops retrying that same version; download_failed, incompatible_archive, insane_distro and locked are retried once after an hour. After fixing the cause, wp option get auto_core_update_failed shows what happened, and a successful wp core update clears the block.

Rollback of failed updates

Three mechanisms exist, each narrow:

  • Core, since 3.7: core updates started by the background updater run with attempt_rollback. If copying the new files fails, WordPress re-installs the current version from a rollback package and records the rollback_was_required error ("Due to an error during updating, WordPress has been restored to your previous version."), which the failure email reports. Only a rollback that fails too counts as critical. It does not detect a new version that installs cleanly but breaks the site.

  • Plugins and themes, manual updates, since 6.3: the old copy is kept in wp-content/upgrade-temp-backup/ and put back if the installation itself fails.

  • Plugins, automatic updates, since 6.6: after auto-updating a plugin that was active, WordPress keeps the site in maintenance mode, waits two seconds and requests the home page with a scrape key. If the response contains a PHP fatal error, the old version is restored from the temporary backup, the error log says the update "contained a fatal error", and the plugin appears in the summary email ("Some plugins have failed to update") under "The following plugins failed to update. If there was a fatal error in the update, the previously installed version has been restored." The same plugin is offered again at the next check.

The second and third are described with their limits (inactive plugins and themes are not checked, non-fatal breakage is not detected, database changes are not undone) in Choosing and Managing Plugins and Themes.

One more limit matters to administrators: a loopback request that fails counts as a fatal error. has_fatal_error() returns true when wp_remote_get() to the home URL returns an error. On the test site, whose home URL (https://lumenpress.test) the server could not reach on port 443, every auto-update of the active Two Factor plugin was undone although 0.17.0 has no fatal error (log from WP_DEBUG_LOG with timestamps removed, and the Lumen policy’s log line at the end):

Automatic updates starting...
  Automatic plugin updates starting...
    Upgrading plugin 'two-factor'...
    Plugin 'two-factor' has been upgraded.
    Scraping home page...
Loopback request failed: cURL error 7: Failed to connect to lumenpress.test:443 after 2 ms: Could not connect to server
    The update for 'two-factor' contained a fatal error. The previously installed version has been restored.
  Automatic plugin updates complete.
Automatic updates complete.
[lumen-updates] plugin Two Factor: plugin_update_fatal_error_rollback_successful

Afterwards wp plugin list still showed 0.16.0, active, with 0.17.0 available. With the home URL reachable from the server, the same update was kept ("The update for 'two-factor' has no fatal errors."). Behind a load balancer, a firewall that blocks the server’s own requests, HTTP basic auth (only forwarded when the cron request itself carried it) or a split DNS, active plugins therefore never auto-update, and the emails report them as failed. Check Tools > Site Health ("Your site can perform loopback requests") on every environment that auto-updates plugins; see Debugging, Site Health and Troubleshooting.

Updating safely

Automatic minor updates are low-risk and should stay on. Major releases, plugin and theme updates, and anything on a site with custom code deserve a process. Lumen Press follows this one:

  1. Staging first: a copy of production with the same PHP version and plugins, refreshed from a production backup (see Importing, Exporting and Migrating). It takes major releases as soon as they ship; the team tests the catalogue, the lumen/latest-books block, the lumen/v1 REST API and the WooCommerce checkout (see Cart and Checkout Blocks) there.

  2. Back up and verify the backup (database and wp-content); see Backups and Disaster Recovery.

  3. Read the changelogs and, for a major release, the Field Guide: removed functions, new PHP minimums, database upgrades.

  4. Update in a quiet window: plugins and themes that support the new version, then core, then translations, then the database upgrade.

  5. Go step by step across large jumps: one-click updates from 3.7 onwards are safe, but the handbook recommends incremental upgrades more than two major releases apart, checking each step’s PHP and database needs (see Requirements and Hosting).

  6. Watch the error log and the admin mailbox for a day.

Updating with WP-CLI

WP-CLI makes each step scriptable and repeatable. The run below took the test site from 7.1.2 to 7.1.3 and updated Two Factor from 0.16.0. It runs as the user that owns the WordPress files; run as another user, WP-CLI creates files that PHP later cannot update. The full command reference is in WP-CLI.

wp core check-update
wp plugin list --update=available --fields=name,version,update_version,auto_update
wp db export - | gzip > "lumen-pre-update-$(date +%F).sql.gz"   # outside the web root
wp core update --minor
wp core update-db
wp plugin update --all --exclude=lumen-catalog
wp theme update --all
wp language core update
wp core verify-checksums
wp plugin verify-checksums --all
version	update_type	package_url
7.1.3	minor	https://downloads.wordpress.org/release/wordpress-7.1.3-partial-2.zip
name	version	update_version	auto_update
two-factor	0.16.0	0.17.0	on
Updating to version 7.1.3 (en_US)...
Downloading update from https://downloads.wordpress.org/release/wordpress-7.1.3-partial-2.zip...
Unpacking the update...
(40 lines of HTML from the plugin and theme translation updates that follow a core update)
Success: WordPress updated successfully.
Success: WordPress database already at latest db version 61833.
Enabling Maintenance mode...
Downloading update from https://downloads.wordpress.org/plugin/two-factor.0.17.0.zip...
Unpacking the update...
Installing the latest version...
Removing the old version of the plugin...
Plugin updated successfully.
Disabling Maintenance mode...
name	old_version	new_version	status
two-factor	0.16.0	0.17.0	Updated
Success: Updated 1 of 1 plugins.
Skipped updates for: lumen-catalog
Success: Theme already updated.
Updating 'Spanish (Spain)' translation for WordPress 7.1.3...
(4 progress lines)
Success: Updated 1/1 translation.
Success: WordPress installation verifies against checksums.
Success: Verified 3 of 3 plugins.

Notes on the commands:

  • wp core update updates to the latest release, including a new major one; --minor stays on the current branch and --version=<x.y.z> picks a version. wp core update-db runs the schema upgrade that the dashboard runs from /wp-admin/upgrade.php; a minor release rarely needs one ("already at latest db version"), a major one often does. On Multisite, wp core update-db --network upgrades every site.

  • After a core update, WordPress updates plugin and theme translations on its own; WP-CLI prints that step as HTML. In the test run the core translation still needed wp language core update; wp language plugin update --all and wp language theme update --all cover the rest.

  • --exclude keeps private plugins that are deployed from Git, such as lumen-catalog, out of the run; WP-CLI reports them as skipped. wp plugin update --all --dry-run lists what would change without changing it.

  • wp plugin update puts the site in maintenance mode around each plugin. For a longer window, wp maintenance-mode activate and deactivate do the same by hand.

  • verify-checksums compares files with WordPress.org’s checksums. It is the quickest proof that an update finished and that nobody altered core or a directory plugin.

Triggering a background run on purpose

To test a policy or reproduce a failed auto-update, run the cron event that a real background run starts from, rather than calling the updater directly:

wp cron event run wp_version_check   # Executed the cron event 'wp_version_check' in 5.541s.

wp cron event run defines DOING_CRON, so the run behaves as in production: Two Factor was updated, checked by the loopback and left active. Calling wp eval 'wp_maybe_auto_update();' also updates, but outside cron Plugin_Upgrader deactivates each plugin before replacing it (in a background run it does not, because "we require a browser to reactivate it"); on the test site, Two Factor ended up updated but inactive. To see what the updater logs, set WP_DEBUG and WP_DEBUG_LOG on a test copy first: every step is written to wp-content/debug.log.

Rolling back core

Downgrading core is discouraged. Newer releases contain security fixes, and a major release can change the database schema in ways the older code does not understand. If it is unavoidable, the handbook’s procedure is a full restore: replace the files with the backed-up ones (everything except wp-config.php) and restore the database backup taken before the update. Without both backups, it calls a successful rollback near impossible.

wp core update --version=7.1.2 --force replaces the files; the test site was moved back that way to repeat the update, and it worked cleanly only because 7.1.2 and 7.1.3 share a database version (61833). Across a major release, wp core update-db merely rewrites the stored db_version and leaves the newer schema and data in place; restore the database instead, as shown in Versions and Release Cycle. With an automatic minor policy, the site also updates itself again at the next check unless the policy changes. Re-install version on Dashboard > Updates re-installs the current version only; it is a repair, not a rollback.

Troubleshooting

Symptom Cause Fix

"Briefly unavailable for scheduled maintenance" stays on screen

An update died and left .maintenance in the WordPress root

Wait ten minutes (WordPress then ignores the file) or delete it; then repeat the update, because it may be half applied

"Another update is currently in progress."

The core_updater.lock option from an interrupted core update (it expires after 15 minutes)

Wait, or wp option delete core_updater.lock once sure no update is running

No automatic update ever happens

WP-Cron does not run, the updater is disabled, a VCS checkout is detected, or WordPress asks for FTP credentials (file ownership, see File Permissions and Filesystem)

wp cron event list, the probe above, and Site Health > Background updates

Active plugins are rolled back after every auto-update, with "contained a fatal error"

The loopback request to the home URL fails

Make the home URL reachable from the server; check Site Health’s loopback test

Site Health fails "WordPress security and maintenance releases are blocked by `define( 'WP_AUTO_UPDATE_CORE', false );`" although a filter allows minor updates

The Site Health test reads the constant only; on the test site the probe confirmed minor updates were still allowed

Remove the constant and express the policy with filters only, or accept the warning

Auto-updates stopped after an "URGENT" email

A critical core failure is recorded in auto_core_update_failed

Restore the site if needed, then update core manually; the next successful update clears the block

Where older books are out of date

  • DISALLOW_FILE_MODS is not just "no updates". One older book presents it as the way to turn updates off. It also removes installing, uploading and deleting plugins and themes and the file editors, and it turns off the background updater too, including core security releases. To change only the update policy, use WP_AUTO_UPDATE_CORE and filters.

  • "Minor" means X.Y.Z. Books that read WordPress versions as major.minor.patch would take 'minor' to mean 7.1 to 7.2. In WordPress, 7.1 to 7.2 is a major release and 'minor' allows only 7.1.x releases.

  • Major releases update themselves on recent installs. Advice written for older sites assumes only minor releases are automatic; any site installed on 5.6 or later receives major releases automatically unless its policy says otherwise.

  • Plugin auto-updates can roll back. Books written for 6.3 and 6.4 predate the 6.6 rollback of active plugins that cause a fatal error after an auto-update, and its dependency on working loopback requests.

  • Filters go in a must-use plugin, not in wp-config.php or a theme’s functions.php (a theme switch drops them).