Je hebt WordPress core bijgewerkt. Sindsdien stuurt elk bezoek aan /wp-admin/ je naar een scherm met de titel Database update vereist, je klikt op WordPress-database bijwerken, het volgende scherm meldt dat de upgrade klaar is, en één klik later sta je weer terug op datzelfde scherm. De lus is bijna altijd een verouderde waarde die in de object cache blijft hangen, geen echt schemaprobleem. In de meeste gevallen los je het binnen vijf minuten op zonder dat je de database aanraakt.
Wat deze fout eigenlijk betekent
Bij elk admin-request controleert WordPress of het databaseschema overeenkomt met de draaiende code. Dat doet het door db_version uit de wp_options-tabel te lezen en te vergelijken met de integer-constante $wp_db_version in wp-includes/version.php. Komen die twee getallen niet overeen, dan blokkeert wp-admin/upgrade.php de normale admintoegang en toont in plaats daarvan het upgrade-scherm. De lus ontstaat zodra de upgrade het nieuwe getal wel netjes naar de database wegschrijft, maar een volgende get_option('db_version') een verouderde kopie uit de object cache terugleest. Daardoor klopt de vergelijking nog steeds niet.
Hoe WordPress bepaalt of het schema actueel is
Twee waarden zijn van belang, en het zijn niet dezelfde:
$wp_db_versionis een hardgecodeerde integer inwp-includes/version.php. Die loopt alleen op als een WordPress-release een echte schemamigratie meeneemt. In WordPress 6.7 en 6.8 staat hij op58975, omdat 6.8 geen schema-aanpassingen had; in de huidige trunk (richting 7.1) staat hij op61833.db_versioninwp_optionsis de integer van de laatst voltooide migratie. WordPress schrijft die metupdate_option()weg aan het einde vanwp_upgrade(), nadatdbDelta()het schema heeft bijgewerkt.
Let op een naamvalkuil: de wpdb::db_version()-methode geeft de versie van de MySQL- of MariaDB-server terug. Dat heeft niets te maken met de schemaversie hierboven. Artikelen die deze twee door elkaar halen, sturen je een doodlopende hoek in.
De check in upgrade.php ziet er ongeveer zo uit:
// wp-admin/upgrade.php (geparafraseerd)
if ( (int) get_option( 'db_version' ) === $wp_db_version ) {
// Geen update nodig, stuur de gebruiker door naar wp-admin.
}
Het is get_option() dat de boel breekt. Die functie leest niet altijd uit de database. Met een persistente object cache leest hij eerst uit die cache, en daar zitten de verouderde waarden.
Belangrijkste oorzaak: een verouderde object-cache.php-drop-in
In bijna alle lus-gevallen is de schemamigratie al bij de eerste klik gewoon geslaagd. wp_upgrade() heeft db_version in wp_options aangepast, twee keer wp_cache_flush() aangeroepen, en je teruggestuurd naar /wp-admin/. Bij het volgende request liep get_option('db_version') via de persistente object cache (Redis, Memcached, of een door je host beheerde variant) en die cache gaf de oude integer terug, omdat de flush het cache-item niet correct heeft geleegd.
Dit is de gedocumenteerde oorzaak in WordPress Trac #26173 en #27669. Het komt vooral voor bij gedeelde Memcached-pools, waar een wp_cache_flush() vanuit het ene PHP-proces niet altijd de cache bereikt die het volgende proces gebruikt. Bij Redis gebeurt hetzelfde wanneer de Redis-Object-Cache-drop-in een flush-mode gebruikt die zich beperkt tot een key-prefix die niet matcht met de option die wordt gelezen.
Staat er een bestand object-cache.php in je wp-content/-map, dan is dat je hoofdverdachte. Je bevestigt het op precies dezelfde manier als ik tijdens een incident: hernoem dat ene bestand en kijk of de fout meteen weg is. De fix hieronder doet exact dat.
Tweede oorzaak: een halve upgrade die version.php heeft achtergelaten
Heb je core handmatig over FTP geüpload en viel de verbinding halverwege weg? Dan kan wp-includes/version.php nog de oude $wp_db_version bevatten terwijl de rest van je install al op de nieuwe versie staat, of net andersom. In dat geval is de mismatch echt en niet door cache veroorzaakt, en helpt het legen van de cache niets. Vergelijk de integer in wp-includes/version.php met de waarde uit de release-notes van die WordPress-versie. Verschilt het, upload wp-includes/ dan opnieuw vanuit een verse download van diezelfde versie en probeer admin daarna nog eens.
Derde oorzaak: bestandsrechten of databaserechten blokkeren de upgrade
Komt minder vaak voor, maar wel even uitsluiten voordat je naar WP-CLI grijpt. Kan wp_upgrade() niet naar de database schrijven omdat de databasegebruiker geen ALTER TABLE-recht heeft, of kan PHP zijn eigen bestanden niet lezen omdat de upgrade halverwege een corrupte dbDelta()-log heeft achtergelaten? Dan mislukt de migratie stilletjes en blijft db_version op de oude waarde staan. In dat geval zit je niet in een stale-cache-lus, maar in een echte mislukte-upgrade-lus. Het signaal: cache legen verandert niets, en wp core update-db (hieronder) meldt een foutmelding in plaats van "no update needed".
Diagnose: in welke lus zit je nu eigenlijk
Twee minuten diagnose besparen je een uur giswerk.
Check 1: lees de echte db_version uit de database, langs alle caches om. Open phpMyAdmin of Adminer, kies je WordPress-database, en draai:
SELECT option_value FROM wp_options WHERE option_name = 'db_version';
Vervang wp_options door je geprefixte tabelnaam als $table_prefix in wp-config.php niet wp_ is.
Check 2: lees $wp_db_version uit PHP. Open wp-includes/version.php in een editor en zoek de regel $wp_db_version = ....
Wat het resultaat je vertelt:
- Komen de twee getallen overeen maar blijft de lus actief? Dan klopt de opgeslagen waarde. Iets leest een verouderde waarde terug via de object cache. Pas de fix in de volgende sectie toe.
- Verschillen ze? Dan heeft de upgrade het nieuwe getal helemaal niet geschreven. Verdenk oorzaak twee (halve upgrade) of oorzaak drie (bestands- en databaserechten). Upload core opnieuw of bekijk je error log voordat je iets anders doet.
Stap voor stap fixen
Fix 1: leeg de persistente cache vanuit je hostingpaneel of plugin
Sommige managed hosts (Kinsta, WP Engine, SiteGround) en sommige plugins (W3 Total Cache, LiteSpeed Cache) hebben een knop Object cache leegmaken of Redis purge in hun hostingpaneel of in de WordPress-adminbalk. Kun je die knop bereiken (soms laat het upgrade-scherm dat nog toe), klik hem dan aan en herlaad /wp-admin/. Dit is de minst ingrijpende fix, maar werkt alleen als de flush-knop ook echt dezelfde cache-backend raakt waar PHP uit leest. Bij gedeelde Memcached is dat geen garantie.
Fix 2: hernoem object-cache.php via je bestandsbeheerder
Hielp de flush-knop niet, of kun je er niet bij? Dan zet je de persistente cache tijdelijk uit:
- Open de bestandsbeheerder van je hostingpaneel, of maak verbinding via SFTP.
- Blader naar
wp-content/. - Hernoem
object-cache.phpnaarobject-cache.php.disabled. - Bezoek
/wp-admin/. Het upgrade-scherm hoort meteen weg te zijn. WordPress valt terug op de in-memory niet-persistente cache, en die kan tussen requests geen verouderde waarden vasthouden. - Zodra je hebt bevestigd dat de lus weg is, ga je naar Plugins. Deactiveer en heractiveer je object-cache-plugin (Redis Object Cache, W3 Total Cache, LiteSpeed Cache, of welke plugin de drop-in oorspronkelijk had geïnstalleerd). De plugin maakt dan zelf een verse
object-cache.phpaan met een schone cache-staat.
Je weet dat het werkt zodra het dashboard normaal laadt en je /wp-admin/ bij volgende bezoeken niet meer op het upgrade-scherm uitkomt. Wil je zeker weten dat het schema op de juiste versie staat? Draai check 1 uit de diagnosesectie nog een keer en bevestig dat de integer overeenkomt met $wp_db_version.
Wil je persistente caching daarna wel gewoon blijven gebruiken? Volg dan mijn walkthrough over hoe je Redis object cache in WordPress goed instelt. Een drop-in die flushes wél netjes propageert is de manier om te voorkomen dat deze lus bij de volgende core-update terugkomt.
Als je SSH-toegang hebt: WP-CLI is de schoonste route
Heb je SSH of een andere manier om WP-CLI uit te voeren, dan omzeilt deze headless fix het hele HTTP-pad waar de lus in zit:
# Leeg wat de object cache nu nog vasthoudt.
wp cache flush
# Draai de schemamigratie direct. Dit roept wp_upgrade() over de CLI aan.
wp core update-db
# Verifieer: dit hoort "no update needed" terug te geven.
wp core update-db --dry-run
Voor een WordPress-multisite draai je het in één keer over het hele netwerk:
wp core update-db --network
wp core update-db roept wp_upgrade() direct aan en print de db_version-integers van voor en na. Omdat het niet via dezelfde persistente verbinding loopt als een browser-request, is de kans veel kleiner dat er een verouderde gecachete waarde terugkomt. Je weet dat het werkt zodra het tweede commando "WordPress database already at latest db_version" meldt en je /wp-admin/ weer kunt openen zonder dat je naar het upgrade-scherm wordt geduwd.
Wat je niet moet doen
Niet handmatig de db_version-rij in wp_options aanpassen om hem gelijk te trekken met $wp_db_version. Dat markeert de migratie als voltooid zonder dat upgrade_all() ooit heeft gedraaid, en die functie voert de per-versie data-migraties uit (upgrade_670(), upgrade_680(), enzovoort). Sla je die over, dan blijft je database in een inconsistente staat hangen die maanden later op onvoorspelbare manieren stuk gaat. Het hoeft trouwens niet eens om het schema te gaan; sommige upgrades werken alleen optionwaarden bij of bouwen caches opnieuw op.
Herhaaldelijk op WordPress-database bijwerken klikken helpt ook niets. Elke klik draait dezelfde wp_upgrade() en leest bij de redirect dezelfde verouderde cachewaarde terug. Het schema was na de eerste klik al goed.
Multisite heeft een extra stap
Op een WordPress-multisite-netwerk draait de netwerkbeheerder de upgrade één keer op netwerkniveau, en daarna heeft elke subsite zijn eigen db_version nodig die wordt bijgewerkt. wp_upgrade() doet dit via upgrade_network(), dat de subsites in groepjes van vijf afloopt en voor elk wpmu_upgrade_site() aanroept. Heb je alleen de lus op de hoofdsite gefixt, dan kunnen subsites de prompt nog steeds tonen als hun eigen beheerder voor het eerst inlogt.
De netste fix voor een heel netwerk is één WP-CLI-commando:
wp core update-db --network
Of de netwerkbeheerder gaat naar Netwerkbeheer > Dashboard > Updates > Netwerk bijwerken en loopt de subsites handmatig door. Inactieve subsites worden overgeslagen en krijgen hun upgrade pas zodra hun eigen beheerder weer inlogt.
Hoe dit verschilt van "Error establishing a database connection"
De twee fouten worden vaak verward omdat in beide het woord "database" voorkomt, maar technisch hebben ze niets met elkaar te maken. Bij een database connection error kan WordPress MySQL of MariaDB überhaupt niet bereiken en is ook de voorkant van je site offline. Bij de database-update-lus heeft WordPress wél verbinding gemaakt, wp_options uitgelezen, en daar een getal teruggekregen dat niet bevalt. De voorkant van de site blijft meestal gewoon werken zolang je in de lus hangt, omdat de lus alleen in /wp-admin/ afgaat. Ligt de voorkant óók plat? Dan zit je met een echt database-verbindingsprobleem, en het oplospad daarvoor is volledig anders.
Nog een verwarring: een terugkerende update-lus op een zwaar gecachete, trage site lijkt soms op een algemeen traag database-probleem, omdat het upgrade-scherm lang doet over renderen en het dashboard bevroren aanvoelt. Ze hebben niets met elkaar te maken. Kun je het upgrade-scherm überhaupt openen, dan staat de database gewoon. De fix zit op het cache-pad, niet op het database-pad.
Wanneer je hulp inroept
Krijg je het met geen van bovenstaande fixes weg, stop dan met handmatig in bestanden prikken en verzamel informatie voor een ticket bij je host of ontwikkelaar. De eerste reactie die je terugkrijgt, is veel bruikbaarder als je dit erbij stuurt:
- De exacte integer in
wp_options.db_versionuit check 1 - De exacte integer in
wp-includes/version.phpuit check 2 - Je WordPress-, PHP- en database (MySQL of MariaDB) versies
- Of
wp-content/object-cache.phpbestaat, en zo ja, welke plugin of host die heeft geïnstalleerd - Je hostingtier en of je op gedeelde Memcached zit of een eigen Redis-instantie hebt
- De output van
wp core update-db --dry-runals je fix 1 hebt gedraaid - Of je rond hetzelfde moment ook PHP-fouten in je error log ziet
Die error log is het stuk dat de meeste mensen overslaan, en juist het meest bruikbare. Heb je hem nog niet aan staan, dan loopt mijn walkthrough over de WordPress debug log inschakelen en lezen precies door welke constanten in wp-config.php je zet en waar het bestand uiteindelijk komt te staan.
Voorkom dat de lus terugkomt
- Gebruik een object-cache-drop-in die flushes goed afhandelt. De Redis-Object-Cache-plugin van Till Krüss is degene die ik vertrouw, en mijn setup-walkthrough loopt door de constanten die voorkomen dat je later met verouderde keys blijft zitten.
- Draai
wp core update-dbover WP-CLI als vast onderdeel van je update-playbook, niet via de browser. Ook als de browserroute werkt, is de CLI sneller en betrouwbaarder op hosts met persistente cache. - Open na een grote core-update één keer
/wp-admin/en check dat je op het dashboard belandt en niet op het upgrade-scherm. De lus op de updatedag opvangen is veel goedkoper dan hem de ochtend erna ontdekken. - Op gedeelde Memcached: zet
WP_CACHE_KEY_SALTinwp-config.phpop iets unieks per site. Een Memcached-instantie delen zonder salt is de gedocumenteerde manier om verouderdedb_version-waarden te krijgen die over sites heen lekken. Het patroon isdefine( 'WP_CACHE_KEY_SALT', md5( DB_NAME . __FILE__ ) );. - Test je update-proces eerst op een stagingkopie. De lus is op staging onschuldig en op productie gênant.