You updated WordPress core. Now every visit to /wp-admin/ redirects to a screen titled Database Update Required, you click Update WordPress Database, the next page says the upgrade is complete, and one click later you are right back on the same screen. The loop is almost always a stale value sitting in the object cache, not a real schema problem. In most cases you can break it in under five minutes without editing the database by hand.
What this error actually means
WordPress checks on every admin request whether the database schema matches the running code. It does that by reading db_version from the wp_options table and comparing it against the integer constant $wp_db_version defined in wp-includes/version.php. If the two numbers do not match, wp-admin/upgrade.php blocks normal admin access and shows the upgrade prompt instead. The loop happens when the upgrade actually succeeds in writing the new value to the database, but a subsequent get_option('db_version') reads a stale copy back from the object cache and the comparison fails again.
How WordPress decides whether the schema is current
Two values matter, and they are not the same thing:
$wp_db_versionis a hardcoded integer inwp-includes/version.php. It only increases when a WordPress release ships an actual schema migration. In WordPress 6.7 and 6.8 it is58975, because 6.8 changed no schema; in current trunk (toward 7.1) it is61833.db_versioninwp_optionsis the integer of the last successfully completed migration. WordPress writes this withupdate_option()at the end ofwp_upgrade()oncedbDelta()has applied the schema changes.
Note the naming trap: the wpdb::db_version() method returns the MySQL or MariaDB server version string. That is unrelated to the schema version above. Articles that mix these two up send you down a dead end.
The check in upgrade.php is roughly:
// wp-admin/upgrade.php (paraphrased)
if ( (int) get_option( 'db_version' ) === $wp_db_version ) {
// No update required, send the user to wp-admin.
}
get_option() is the bit that breaks. It does not always read from the database. When a persistent object cache is in place, it reads from that cache first, and that is where stale values live.
The most common cause: a stale object-cache.php drop-in
In almost every loop case, the schema migration ran successfully on the first click. wp_upgrade() updated db_version in wp_options, called wp_cache_flush() twice, and redirected you back to /wp-admin/. On the next request, get_option('db_version') went through the persistent object cache (Redis, Memcached, or a hosting-managed equivalent) and the cache returned the pre-upgrade integer because the flush did not invalidate the entry properly.
This is the documented root cause in WordPress Trac #26173 and #27669. It is especially common with shared Memcached pools, where a wp_cache_flush() from one PHP process does not always reach the cache shared with the next one. With Redis, the same thing happens when the Redis Object Cache drop-in uses a flush mode that scopes to a key prefix that does not match the option being read.
If your wp-content/ directory contains a file called object-cache.php, this is your prime suspect. You can confirm it the same way I do during incident triage: rename that one file and see if the error disappears immediately. The fix below does exactly that.
Second cause: a partial upgrade that left version.php behind
If you uploaded WordPress core manually over FTP and the connection dropped halfway through, wp-includes/version.php may still hold the old $wp_db_version integer while the rest of the install is on the new version, or the other way around. In that case the mismatch is real, not cached, and clearing the cache will not help. Compare the integer in wp-includes/version.php against the value listed in the WordPress release notes for the version you intended to install. If they differ, re-upload wp-includes/ from a fresh download of the same version and try the admin again.
Third cause: file permissions blocking the upgrade
Less common but worth ruling out before you reach for WP-CLI. If wp_upgrade() cannot write to the database (because the database user lacks ALTER TABLE rights) or if PHP cannot read its own files (because the upgrade left a half-written dbDelta() log behind), the migration silently fails and db_version stays at the old value. In this case you are not in a stale-cache loop, you are in a real failed-upgrade loop. The signal: clearing the cache changes nothing, and wp core update-db (below) reports an error instead of "no update needed".
Diagnose: which loop are you in
Two minutes of diagnosis saves you an hour of guessing.
Check 1: read the actual db_version from the database, bypassing all caches. Open phpMyAdmin or Adminer, select your WordPress database, and run:
SELECT option_value FROM wp_options WHERE option_name = 'db_version';
Replace wp_options with your prefixed table name if $table_prefix in wp-config.php is not wp_.
Check 2: read $wp_db_version from PHP. Open wp-includes/version.php in any editor and find the line $wp_db_version = ....
What the result tells you:
- If the two numbers match but the loop persists, the stored value is correct. Something is reading a stale value back through the object cache. Apply the fix in the next section.
- If the two numbers do not match, the upgrade has not actually written the new value. Suspect cause two (partial upgrade) or cause three (file permissions and database permissions). Re-upload core or check your error log before doing anything else.
Step-by-step fix
Fix 1: flush the persistent cache from your hosting panel or plugin
Some managed hosts (Kinsta, WP Engine, SiteGround) and some plugins (W3 Total Cache, LiteSpeed Cache) expose a Flush Object Cache or Purge Redis button in the hosting control panel or in the WordPress admin bar. If you can reach that button (sometimes the upgrade screen still allows it), click it and reload /wp-admin/. This is the least disruptive fix, but it only works when the flush button actually reaches the same cache backend that PHP reads from. On shared Memcached this is not guaranteed.
Fix 2: rename object-cache.php through your file manager
If the flush button did not help or you cannot reach it, disable the persistent cache temporarily:
- Open your hosting control panel's file manager, or connect over SFTP.
- Browse to
wp-content/. - Rename
object-cache.phptoobject-cache.php.disabled. - Visit
/wp-admin/. The upgrade screen should be gone immediately. WordPress will fall back to its in-memory non-persistent cache, which cannot hold stale values across requests. - Once you confirm the loop is broken, go to Plugins, deactivate and reactivate your object-cache plugin (Redis Object Cache, W3 Total Cache, LiteSpeed Cache, or whichever installed the drop-in originally). The plugin will recreate a fresh
object-cache.phpwith a clean cache state.
You know it worked when the dashboard loads normally and /wp-admin/ no longer shows the upgrade screen on subsequent visits. If you want to be sure the schema is on the right version, run check 1 from the diagnose section after the fix and confirm the integer matches $wp_db_version.
If you want to keep persistent caching long-term, see my walkthrough on how to set up the Redis object cache in WordPress correctly. A drop-in that propagates flushes properly is what stops this loop from coming back on the next core update.
If you have SSH access: WP-CLI is the cleanest path
If you have SSH or any way to run WP-CLI, this headless fix bypasses the entire HTTP request path that the loop lives on:
# Flush whatever the object cache currently holds.
wp cache flush
# Run the schema migration directly. This calls wp_upgrade() over the CLI.
wp core update-db
# Verify: this should report "no update needed".
wp core update-db --dry-run
For WordPress Multisite, run it across the network in one go:
wp core update-db --network
wp core update-db calls wp_upgrade() directly and reports the before and after db_version integers. Because it does not go through the same persistent connection a browser request does, it is far less likely to read a stale cached value back. You know it worked when the second command reports "WordPress database already at latest db_version" and you can load /wp-admin/ without being redirected to the upgrade screen.
What not to do
Do not edit the db_version row in wp_options directly to match $wp_db_version. That marks the migration as complete without ever running upgrade_all(), which is the function that runs each per-version data migration (upgrade_670(), upgrade_680(), and so on). Skipping those leaves your database in an inconsistent state that breaks in unpredictable ways months later. The schema may not even be the part being migrated; some upgrades only update option values or rebuild caches.
Clicking Update WordPress Database repeatedly does nothing useful either. Each click runs the same wp_upgrade() and reads the same stale cache value on the redirect. The schema was already correct after the first click.
Multisite has an extra step
On a WordPress Multisite network, the network admin runs the upgrade once at the network level, then each subsite needs its own db_version brought forward. wp_upgrade() does this through upgrade_network(), which iterates the subsites five at a time and calls wpmu_upgrade_site() for each. If you only fixed the loop on the main site, individual subsites can still show the prompt the first time their admin logs in.
The cleanest fix for an entire network is one WP-CLI command:
wp core update-db --network
Alternatively, the network admin can go to Network Admin > Dashboard > Updates > Update Network and step through the subsites manually. Inactive subsites are skipped and will be upgraded the next time their site admin logs in.
How this differs from "Error establishing a database connection"
The two errors get confused often because both contain the word "database", but they have nothing in common at the technical level. In a database connection error, WordPress cannot reach MySQL or MariaDB at all and the front of your site is also offline. In the database update loop, WordPress connected successfully, queried wp_options, and got back a number it does not like. The front of the site usually still works while you are stuck in the loop, because the loop only triggers in /wp-admin/. If the front is also down, you are dealing with a real database connection problem instead, and the fix path is completely different.
A second confusion: a recurring update loop on a heavily cached, slow site can look like a generic slow database problem, because the upgrade screen takes a long time to render and the dashboard feels frozen. They are unrelated. If you can reach the upgrade screen at all, the database is up. The fix is on the cache path, not the database path.
When to escalate
If none of the fixes above clears the loop, stop hand-editing files and gather information for a host or developer ticket. The first response you get back will be far more useful if you include:
- The exact integer in
wp_options.db_versionfrom check 1 - The exact integer in
wp-includes/version.phpfrom check 2 - Your WordPress, PHP, and database (MySQL or MariaDB) versions
- Whether
wp-content/object-cache.phpexists, and if so, which plugin or host installed it - Your hosting tier and whether you are on shared Memcached or a per-site Redis instance
- The output of
wp core update-db --dry-runif you ran fix 1 - Whether you also see PHP errors in your error log around the same time
The error log is the part most people skip and it is often the most useful. If you have not turned it on yet, my walkthrough on enabling and reading the WordPress debug log covers exactly which constants in wp-config.php to set and where the file ends up.
Prevent the loop from coming back
- Use an object-cache drop-in that handles flushes correctly. The Redis Object Cache plugin from Till Krüss is the one I trust, and the setup walkthrough covers the constants that prevent stale-key issues.
- Run
wp core update-dbover WP-CLI as part of your update playbook, not the browser path. Even when the browser path works, the CLI path is faster and more reliable on hosts with persistent caches. - After a major core update, reload
/wp-admin/once and check that you land on the dashboard, not on the upgrade screen. Catching the loop on the day of the update is far cheaper than discovering it the morning after. - On shared Memcached, set
WP_CACHE_KEY_SALTinwp-config.phpto something unique per site. Sharing a Memcached instance without a salt is the documented way staledb_versionvalues appear across sites. The pattern isdefine( 'WP_CACHE_KEY_SALT', md5( DB_NAME . __FILE__ ) );. - Test your update process on a staging copy first. The loop is harmless on staging, embarrassing on production.