Hoge admin-ajax.php belasting in WordPress diagnosticeren

Een hosting-dashboard of APM-trace die admin-ajax.php als top-URL toont, vertelt op zichzelf bijna niets. Het bestand is een dispatcher: het routeert elk AJAX-verzoek dat WordPress-plugins doen naar de juiste PHP-handler. De echte vraag is welke action de load veroorzaakt en waarom. Dit artikel laat zien hoe je de veroorzaker identificeert, de vijf meest voorkomende oorzaken oplost en de 'oplossingen' vermijdt die het juist erger maken.

Wat admin-ajax.php is en wie het aanroept

admin-ajax.php is geen feature. Het is een dispatcher. Elk AJAX-verzoek dat een WordPress-plugin doet, landt op dezelfde URL (/wp-admin/admin-ajax.php) en het bestand leest de action-parameter uit de POST-body om te bepalen welke PHP-functie het verzoek moet afhandelen. Voor ingelogde gebruikers is de hook wp_ajax_{$action}, voor anonieme bezoekers wp_ajax_nopriv_{$action}.

De performancekosten zitten in de bootstrap. Elke aanroep naar admin-ajax.php laadt de volledige WordPress admin-omgeving: core, alle actieve plugins, alle hooks en de admin_init-action. Een simpele "geef een JSON-teller terug"-action boot tientallen megabytes PHP voordat er ook maar een regel plugincode draait. De REST API (onderdeel van core sinds WordPress 4.7, december 2016) slaat die admin-bootstrap over en is doorgaans 15–25% sneller voor vergelijkbare operaties, maar admin-ajax.php is niet deprecated. Honderden plugins gebruiken het nog, en de Heartbeat API draait er by design op.

Daardoor is "admin-ajax.php is je topverkeer" zoiets als "je startscherm is je meestgebruikte app." Het startscherm is niet het probleem. De app erachter wel.

admin-ajax.php vs wc-ajax vs de REST API

Niet elk AJAX-verzoek op een WordPress-site gaat daadwerkelijk door admin-ajax.php. WooCommerce introduceerde een eigen lichtgewicht endpoint in versie 2.4 (2015): /?wc-ajax=get_refreshed_fragments. Dit loopt via de wp_loaded-hook in plaats van de admin-ajax dispatcher, waardoor admin_init niet afvuurt. De URL is anders. De PHP-bootstrapkosten zijn vergelijkbaar maar niet identiek.

Als je access log of APM-tool veel verkeer toont naar /?wc-ajax= in plaats van /wp-admin/admin-ajax.php, dan is het probleem WooCommerce-specifiek en is de Heartbeat API niet betrokken.

De REST API (/wp-json/) is een derde, apart endpoint. Block editor autosave in WordPress 5.0+ gebruikt de REST API, niet admin-ajax.php. Steeds meer plugins migreren ernaartoe. Als de load onder /wp-json/ verschijnt, is dit artikel niet van toepassing.

Endpoint URL-patroon Bootstrap Typische aanroepers
admin-ajax.php POST /wp-admin/admin-ajax.php Volledige admin-omgeving Heartbeat, legacy plugins, formulierplugins, analytics-beacons
wc-ajax GET /?wc-ajax=<action> wp_loaded (lichter) WooCommerce cart fragments, add-to-cart
REST API GET/POST /wp-json/... REST-bootstrap (lichtst) Block editor, moderne plugins, oEmbed

Symptomen die wijzen op admin-ajax.php load

Het probleem ziet er anders uit, afhankelijk van waar je kijkt:

In hosting-dashboards. De tabel "top-URL's op aantal verzoeken" of "top-URL's op CPU-tijd" toont /wp-admin/admin-ajax.php bovenaan, soms met duizenden hits per uur. Dit is de meest voorkomende aanleiding voor de bekende e-mail van je hoster. Het probleem: access logs registreren alleen de URL, niet de POST-body. De action=-parameter die de veroorzaker identificeert, is onzichtbaar.

In APM-tools (New Relic, Datadog). De transactie WebTransaction/Action/admin-ajax.php staat bovenaan "meest tijdrovend" of "hoogste throughput". New Relic registreert dit als een enkele transactie, ongeacht welke action erin draait. De New Relic Reporting for WordPress plugin (van 10up) verrijkt transactienamen met WordPress-specifieke metadata, waardoor de trage action zichtbaar wordt in de segmentboom.

In browser developer tools. Op de voorkant laat het Network-tabblad in DevTools herhaalde admin-ajax.php POST-verzoeken zien die bij elke page load afvuren. Het Payload-tabblad onthult de action=-waarde. Zie je dit op de publieke site (niet in wp-admin), dan genereren anonieme bezoekers het ook.

In servermetrics. Het aantal PHP-FPM workers blijft hoog, ook buiten piekuren, en de pool raakt verzadigd. Omdat admin-ajax.php verzoeken uncacheable POST-requests zijn, claimen ze elk een PHP worker voor de volledige duur van het antwoord.

De veroorzaker identificeren

Het kernprobleem bij diagnostiek is dat standaard webserver access logs geen POST-bodies registreren. De URL /wp-admin/admin-ajax.php staat op elke regel, maar de action=-parameter die de verantwoordelijke plugin identificeert, is nergens te vinden. Je hebt een van deze methoden nodig:

Browser DevTools (snelst voor frontend-verkeer). Open Chrome of Firefox DevTools, schakel naar het Network-tabblad, filter op Fetch/XHR en herlaad de pagina of doorloop de gebruikersflow. Klik op een admin-ajax.php-regel en open het Payload-tabblad. Het action-veld is in plain text leesbaar.

Query Monitor (server-side, alleen voor admins). Installeer Query Monitor en reproduceer de flow. Het AJAX-paneel toont per verzoek de action-naam, het aantal queries, het geheugengebruik en de afgevuurde hooks. Output is standaard admin-only; gebruik QM's auth cookie-mechanisme om als niet-admin te testen.

WP Engine admin-ajax logging. Op WP Engine hosting voeg je dit toe aan wp-config.php:

define( 'WPE_MONITOR_ADMIN_AJAX', true );

Hiermee worden POST-bodies weggeschreven naar wp-content/__wpe_admin_ajax.log. Parseer het:

# Toon de 20 meest voorkomende actions
grep "action" wp-content/__wpe_admin_ajax.log \
  | sort | uniq -c | sort -rn | head -20

APM-transactie tracing. In New Relic One: APM, dan Transactions, sorteer op "Most time consuming", filter op admin-ajax.php, drill in op een transactie-trace en zoek de trage hook in de segmentboom.

De plugin vinden bij een action-naam. Zodra je de action-naam hebt (zeg adrotate_impression), is de snelste aanpak even de action-naam online opzoeken: een zoekopdracht naar wp_ajax adrotate_impression levert bijna altijd de documentatie of het supportforum van de plugin op. Als de action-naam begint met een herkenbaar pluginprefix (woocommerce_, elementor_, wpforms_), is de eigenaar sowieso duidelijk.

Als je SSH-toegang hebt: zoek direct in je pluginmap:

grep -r "wp_ajax_nopriv_adrotate_impression\|wp_ajax_adrotate_impression" \
  wp-content/plugins/

Dit geeft je de exacte plugin en het bestand dat de handler bezit.

Heartbeat-pieken oplossen

De Heartbeat API stuurt action=heartbeat naar admin-ajax.php met standaardintervallen van 15 seconden in de post-editor en 60 seconden op andere wp-admin schermen. Als je logs bevestigen dat heartbeat de topaction is, dan is throttlen de oplossing, niet uitschakelen.

Throttle het interval naar 60 seconden overal:

// In een mu-plugin of functions.php
add_filter( 'heartbeat_settings', function( $settings ) {
    $settings['interval'] = 60; // toegestaan bereik: 15–300 seconden
    return $settings;
} );

Schakel Heartbeat uit op admin-schermen waar autosave niet uitmaakt (alles behalve de post-editor):

add_action( 'admin_init', function() {
    $screen = get_current_screen();
    if ( $screen && $screen->base !== 'post' ) {
        wp_deregister_script( 'heartbeat' );
    }
} );

Schakel Heartbeat niet helemaal uit. Dat breekt autosave en post locking. Met 50 redacteuren in de post-editor ga je van ruwweg 200 uncacheable PHP-verzoeken per minuut (bij 15-seconde intervallen) naar ongeveer 50 (bij 60-seconde intervallen). Als de Heartbeat API conceptartikel op jouw situatie van toepassing is, vind je daar het volledige verhaal.

Verificatie: na het deployen van het filter open je de post-editor, open je DevTools Network-tabblad en controleer je dat admin-ajax.php-verzoeken met action=heartbeat nu met 60-seconde intervallen verschijnen in plaats van 15.

Pluginveroorzaakte pieken oplossen

Als de topaction niet heartbeat is maar iets als woocommerce_get_cart_fragments, adrotate_impression, een page builder auto-draft action of een chatplugin session keepalive, hangt de oplossing af van de aanroeper.

WooCommerce cart fragments

De woocommerce_get_cart_fragments-action ververst het mini-cart widget bij elke page load. Op WooCommerce-versies voor 7.8 werd het wc-cart-fragments.js-script op elke pagina geladen, ongeacht of er een cart widget bestond. WooCommerce 7.8 (2023) veranderde dit naar conditioneel laden: het script vuurt alleen als een Cart Widget gerenderd is of de shortcode aanwezig is.

Fix: update WooCommerce naar 7.8+. Als een third-party theme of plugin wc-cart-fragments geforceerd enqueued, dequeue het conditioneel op niet-shoppagina's:

add_action( 'wp_enqueue_scripts', function() {
    if ( ! is_woocommerce() && ! is_cart() && ! is_checkout() ) {
        wp_dequeue_script( 'wc-cart-fragments' );
    }
}, 99 );

Het WooCommerce Cart block (de block editor-versie, niet de legacy widget) gebruikt de fragments-API helemaal niet.

Analytics- en ad-rotatieplugins

Plugins die impressie-beacons of tracking-events via admin-ajax.php afvuren voor elke bezoeker zijn het duurste patroon. Een real-world WP Engine admin-ajax loganalyse vond een enkele AdRotate-installatie die 47.608 calls genereerde. De fix is meestal de plugin vervangen door een die client-side tracking of een REST API-endpoint gebruikt, of de feature helemaal uitschakelen.

Page builder auto-draft saves

Elementor, Avada en Visual Composer maken allemaal admin-ajax.php calls voor auto-draft saves en preview-refreshes. Die vuren alleen voor ingelogde editors, dus ze tellen pas mee op sites met veel gelijktijdige backendgebruikers. Throttling wordt doorgaans afgehandeld in het instellingenpaneel van de builder zelf.

Verificatie na elke pluginfix: monitor na de wijziging het admin-ajax log of je APM-tool gedurende 24 uur. Het aantal verzoeken van de specifieke action zou naar bijna nul moeten dalen, of naar het verwachte tarief.

Waarom admin-ajax.php cachen niet de oplossing is

POST-verzoeken naar admin-ajax.php zijn by design niet cachebaar. Standaard pagecaches (Nginx FastCGI cache, Varnish, WP Rocket, W3 Total Cache) sluiten ze terecht uit. Sommige CDN-configuraties kunnen geforceerd POST-responses cachen, maar dat voor admin-ajax.php doen veroorzaakt echte schade:

  • Gecachte action=heartbeat-responses betekenen dat alle gebruikers dezelfde autosave-status delen. Risico op dataverlies.
  • Gecachte cart fragment-responses betekenen dat alle bezoekers dezelfde winkelwagen zien. Een WooCommerce-shop die de winkelwagen van de ene bezoeker aan de andere serveert, is een afrekenramp.
  • Gecachte nonce-gated actions serveren verlopen nonces. WordPress nonces verlopen na 12–24 uur, en een gecachte nonce-response breekt formulierinzendingen en beveiligingscontroles.

De oplossing is het aantal verzoeken verminderen, niet de responses cachen. Als een plugin-action geschikt is voor caching, is het juiste pad die te migreren naar een GET-gebaseerd REST API-endpoint dat veilig gecacht kan worden.

Wil je een compleet beeld van hoe WordPress cachelayers samenwerken en waar bypasses optreden, lees dan waarom je WordPress cache niet werkt.

Waarom admin-ajax.php blokkeren niet de oplossing is

admin-ajax.php leeft onder /wp-admin/, maar wordt legitiem aangeroepen vanaf de publieke voorkant. Plugins die live search, AJAX-contactformulieren, winkelwagenupdates en infinite scroll bieden, sturen allemaal verzoeken naar admin-ajax.php vanuit anonieme bezoekers. Het bestand blokkeren breekt:

  • Elke plugin-AJAX feature op de voorkant
  • WordPress automatische update-voortgangsrapportage
  • Elke plugin die wp_ajax_nopriv_ gebruikt voor publieke features

Rate limiting per IP op webserver- of CDN-niveau (Nginx limit_req_zone, Cloudflare rate rules) is de juiste aanpak voor bots die admin-ajax.php bestoken. Blokkeer bots, niet het bestand.

Wanneer wp-cron via admin-ajax.php het echte probleem is

WordPress wordt geleverd met ALTERNATE_WP_CRON, een modus waarbij geplande taken afvuren via een redirect naar admin-ajax.php?action=wp_cron. Sommige hosters schakelen dit standaard in. Het resultaat is dat elke cron-run (plugin-updatechecks, backupschema's, e-mailwachtrijen, WooCommerce orderopruiming) via admin-ajax.php loopt en in je access log verschijnt onder dezelfde generieke URL.

Als je loganalyse een hoog aandeel verzoeken toont met action=wp_cron, is de oplossing WP-Cron vervangen door een echte systeemcron zodat geplande taken op een voorspelbaar interval draaien in plaats van mee te liften op bezoekersverkeer.

Wanneer escaleren

Als je de action hebt geidentificeerd, de bijbehorende fix hebt toegepast en de admin-ajax.php load na 24 uur niet merkbaar is gedaald, verzamel dan het volgende voordat je contact opneemt met je hoster of een developer:

  • De top 10 actions uit je admin-ajax log of APM-transactieoverzicht
  • PHP-versie en WordPress-versie (zichtbaar in wp-admin onder Gereedschap, dan Sitegezondheid, dan Info; of via wp core version --extra als je WP-CLI hebt)
  • Lijst actieve plugins (zichtbaar in wp-admin onder Plugins; of via wp plugin list --status=active met WP-CLI)
  • Of het verkeer frontend is (anonieme bezoekers) of backend (ingelogde gebruikers), zichtbaar aan de Referer-header in access logs
  • PHP-FPM poolconfiguratie: pm, pm.max_children, pm.max_requests waarden (vraag je hoster als je geen directe toegang hebt)
  • Of ALTERNATE_WP_CRON is ingeschakeld in wp-config.php (controleer via de bestandsbeheerder van je hostingpanel of SFTP)
  • Stappen die je al hebt geprobeerd, met voor- en na-vergelijking van het aantal verzoeken

Herhaling voorkomen

  • Audit nieuwe plugins voor activatie. Controleer of een plugin wp_ajax_nopriv_-actions registreert die bij elke page load afvuren. Query Monitor toont dit in real time.
  • Geef de voorkeur aan plugins die de REST API gebruiken. De REST API slaat de admin-bootstrap over. Een plugin die /wp-json/ gebruikt voor zijn AJAX in plaats van admin-ajax.php verbruikt minder CPU per verzoek.
  • Stel een echte systeemcron in. Het uitschakelen van ALTERNATE_WP_CRON en WP-Cron draaien vanuit een crontab elimineert de hele klasse cron-via-admin-ajax verkeer.
  • Monitor de action-breakdown, niet alleen de URL. Een maandelijkse check van je top admin-ajax actions (via Query Monitor, APM of een 15 minuten durende WP Engine loganalyse) vangt nieuwe boosdoeners voordat ze een loadprobleem worden.

Klaar met terugkerende traagheid?

Traagheid komt vaak terug na snelle fixes. Professioneel onderhoud houdt updates, caching en limieten consequent op orde.

Bekijk WordPress onderhoud

Doorzoek deze site

Begin met typen om te zoeken, of blader door de kennisbank en blog.