Kurz und einfach erklärt
WordPress kann langsam sein, obwohl ein Cache läuft. Der Cache hilft nur bei fertigen Seiten. Manche Anfragen gehen trotzdem direkt an den Server.
WP-Cron macht geplante Aufgaben. Es startet nur, wenn jemand die Seite aufruft. Mit Cache passiert das manchmal zu spät. Ein fester Zeitplan auf dem Server ist besser.
Offene Fenster im Bereich für die Redaktion melden sich oft beim Server. Das heißt Heartbeat. Man kann den Takt langsamer stellen.
Manche Themes laden Inhalte auf einem Umweg nach. Der Cache kann diese Antworten nicht speichern. Oft kann der Inhalt gleich in die Seite.
Wir messen zuerst, welche Anfragen den Server belasten. Dann stellen wir um und messen noch einmal.
Der Seiten-Cache läuft, die Startseite kommt sofort, und trotzdem klagen Redaktion und Kundschaft über ein träges WordPress: Der Warenkorb hängt, die Suche lässt auf sich warten, im Editor dreht sich das Speichersymbol. Der Widerspruch löst sich auf, wenn man nicht die gecachten Seiten betrachtet, sondern die Anfragen, die der Cache gar nicht zu sehen bekommt. WordPress läuft auf rund 64 Prozent (Web Almanac 2025) der Websites mit erkennbarem Content-Management-System, und fast jede dieser Installationen kennt drei Quellen solcher Anfragen: WP-Cron, das bei Seitenaufrufen nach fälligen Aufgaben sucht, den Heartbeat offener Redaktionsfenster und admin-ajax-Aufrufe von Theme und Erweiterungen im Frontend. Dieser Beitrag zeigt, wie Sie den Anteil ungecachter PHP-Anfragen je Endpunkt im Access-Log messen, WP-Cron auf den System-Cron umstellen, den Heartbeat drosseln, ohne die Bearbeitungssperre zu verlieren, und Frontend-Aufrufe auf cachebare Wege bringen. Die Angaben zum Kern stützen sich auf die Entwicklerdokumentation von WordPress und den Quellcode der Version 7.1.
Das Wichtigste in Kürze
- WP-Cron prüft bei Seitenaufrufen, ob Aufgaben fällig sind. Liefert der Seiten-Cache die Seite aus, ohne dass WordPress startet, entfällt dieser Anstoß, und geplante Aufgaben warten auf die nächste ungecachte Anfrage.
- Jedes offene Redaktionsfenster fragt per Heartbeat alle 15 bis 120 Sekunden (WordPress Plugin Handbook) über admin-ajax beim Server an. Im Beitragseditor endet der Takt erst nach 60 Minuten (WordPress-Quellcode) ohne Aktivität.
-
admin-ajax.phplädt für jede Anfrage den kompletten Kern samt Verwaltungsfunktionen und verbietet das Zwischenspeichern. Ein Zähler oder Nachlader im Frontend macht so aus jedem gecachten Seitenaufruf wieder eine PHP-Anfrage. - Gemessen wird je Endpunkt: das Access-Log nach
wp-cron.php,admin-ajax.phpund/wp-json/gruppieren, die Antwortzeit mitschreiben und die admin-ajax-Aktion separat protokollieren, weil sie bei POST nicht in der Adresse steht. - Mit
DISABLE_WP_CRONund einem System-Cron, derwp cron event run --due-nowaufruft, laufen fällige Aufgaben pünktlich und außerhalb des Worker-Vorrats des Webservers, auch beim Aktionsstart um Mitternacht. - Außerhalb des Editors lässt sich der Heartbeat per
heartbeat_settingsauf 120 Sekunden (WordPress-Quellcode) strecken. Länger schränkt die Bearbeitungssperre ein, die nach 150 Sekunden (WordPress-Quellcode) ohne Signal abläuft.
Warum der Seiten-Cache das Backend nicht entlastet
Ein Seiten-Cache speichert fertiges HTML und gibt es an die nächsten Besucher weiter, ohne dass WordPress startet. Das wirkt für alles, was für alle gleich aussieht und per GET abgerufen wird: Startseite, Beiträge, Kategorien. Alles andere landet weiter bei PHP. Dazu gehören Anfragen angemeldeter Personen, Formular- und Warenkorbaktionen, Aufrufe der REST-API mit persönlichem Bezug und drei Endpunkte, die in fast jeder Installation laufen: wp-cron.php, wp-admin/admin-ajax.php und der Heartbeat, der ebenfalls über admin-ajax.php spricht.
Jede dieser Anfragen belegt einen PHP-Worker, solange sie läuft. Der Vorrat ist begrenzt: Die mitgelieferte Beispielkonfiguration von PHP-FPM setzt pm.max_children auf 5 (PHP-Quellcode) und vermerkt selbst, dass diese Vorgaben für einen Server mit wenig Ressourcen gedacht sind. Hoster und Distributionen setzen eigene Werte, das Prinzip bleibt: Sind alle Worker beschäftigt, wartet die nächste Anfrage in der Warteschlange, bevor eine Zeile PHP läuft. Woran sich dieser Zustand auf der Statusseite von FPM erkennen lässt, beschreibt der Abschnitt zur Worker-Sättigung.
Für Besucherinnen und Besucher zeigt sich das an den Stellen, die der Cache ohnehin nicht abdeckt. Die Startseite bleibt schnell, aber der Klick in den Warenkorb, die Suche oder das Absenden eines Formulars wartet auf einen freien Worker. Die Time to First Byte dieser Anfragen steigt, während jede Messung der Startseite grün bleibt. web.dev nennt 0,8 Sekunden (web.dev) oder weniger als guten TTFB-Wert; ungecachte Anfragen sind die ersten, die diese Marke unter Last verfehlen. Wie sich die Server-Antwortzeit gezielt senken lässt, zeigt die Seite zur TTFB-Optimierung.
Abgrenzung
autoload-Optionen und den persistenten Object-Cache behandelt der Beitrag WordPress Performance: häufige Bremsen lösen. Warum ein einzelnes Cookie die Trefferquote des Caches drückt, zeigt Ein Cookie zerlegt den Cache. Hier geht es um die Anfragen, die am Cache vorbeigehen müssen, und um die Frage, wie viele davon wirklich nötig sind.WP-Cron: Aufgaben, die ein Seitenaufruf anstößt
WordPress hat keinen eigenen Dienst, der im Hintergrund auf die Uhr schaut. Stattdessen prüft der Kern bei Anfragen, ob geplante Aufgaben fällig sind. Das Plugin-Handbuch beschreibt das Verfahren in zwei Sätzen:
WP-Cron works by checking, on every page load, a list of scheduled tasks to see what needs to be run. Any tasks due to run will be called during that page load.
Zu diesen Aufgaben gehören laut Handbuch die Suche nach Aktualisierungen und das Veröffentlichen geplanter Beiträge (WordPress Plugin Handbook). Erweiterungen hängen eigene Aufgaben an: Newsletter-Versand, Feed-Importe, Sicherungen, das Aufräumen von Protokollen oder das Vorwärmen eines Caches. Im Kern hängt der Ablauf an init, also an jeder Anfrage, die WordPress vollständig startet (WordPress-Quellcode). Ist etwas fällig, schickt spawn_cron() eine nicht blockierende Loopback-Anfrage an wp-cron.php, eine Anfrage des Servers an sich selbst (WordPress Code Reference). Eine Sperre verhindert, dass das öfter als einmal alle 60 Sekunden (WordPress Code Reference) geschieht.
Nicht blockierend heißt nicht kostenlos. Die Code-Referenz hält fest, dass die als Zeitgrenze gesetzten 0,01 Sekunden bis zu 1 Sekunde dauern können (WordPress Code Reference). Solange der Anstoß bei wp_loaded lief, musste das HTML so lange warten, und der TTFB des Besuchers stieg. Seit Version 6.9 (WordPress Code Reference) startet WordPress den Anstoß erst bei shutdown, wenn die Seite schon unterwegs ist. Wer noch eine ältere Version betreibt, findet hier eine mögliche Ursache für einzelne Ausreißer in der Antwortzeit, die nur auftreten, wenn gerade eine Aufgabe fällig ist, und sich deshalb schwer nachstellen lassen.
Die eigentliche Arbeit erledigt danach wp-cron.php. Die Datei beendet die HTTP-Antwort früh, wenn PHP-FPM das erlaubt, und arbeitet erst danach die Aufgaben ab (WordPress-Quellcode). Der Besucher, der den Lauf ausgelöst hat, wartet also nicht darauf. Der Worker aber bleibt belegt, bis die letzte fällige Aufgabe fertig ist, und ein Import oder ein Newsletter-Versand kann dauern. In dieser Zeit fehlt er allen anderen ungecachten Anfragen.
Der Cache verschiebt geplante Aufgaben
Mit einem vollständigen Seiten-Cache dreht sich das Problem um. Liefert der Cache eine Seite aus, bevor WordPress bis init gelangt, sieht der Kern diesen Aufruf gar nicht und kann auch nichts anstoßen. Auf einer gut gecachten Website mit wenig Betrieb im Backend vergehen so mitunter Stunden, bis die nächste ungecachte Anfrage eintrifft. Das Handbuch nennt die Folge selbst: Plant man eine Aufgabe für 14 Uhr und kommt bis 17 Uhr kein Seitenaufruf, kann es zu Terminfehlern kommen (WordPress Plugin Handbook). Im Website-Zustand meldet WordPress dann ein verspätetes geplantes Ereignis, verbunden mit dem Hinweis, dass geplante Beiträge oder automatische Aktualisierungen womöglich nicht wie vorgesehen laufen (WordPress-Quellcode).
WP-Cron ist damit zugleich zu häufig und zu selten unterwegs: zu häufig, weil jeder Lauf mitten im Besucherverkehr einen Worker bindet, und zu selten, weil gecachte Aufrufe keinen Lauf auslösen. Beides löst der Umstieg auf einen System-Cron, der weiter unten beschrieben ist.
Der Heartbeat: offene Redaktionsfenster als Dauerlast
Der Heartbeat ist eine Abfrageschnittstelle im Kern. Das Handbuch beschreibt ihn als einfaches Polling, das Aktualisierungen nahezu in Echtzeit erlaubt (WordPress Plugin Handbook). Beim Laden der Seite richtet der Browser einen Takt ein, der alle 15 bis 120 Sekunden (WordPress Plugin Handbook) Daten an den Server schickt. Auf dem Server nimmt ein admin-ajax-Handler die Daten an, bereitet die Antwort vor und gibt sie als JSON zurück.
Wozu das dient, steht im Kopf der Skriptdatei: Der Takt transportiert Bearbeitungssperren für Beiträge, Warnungen vor dem Ablauf der Anmeldung, das automatische Speichern und verwandte Aufgaben, solange jemand angemeldet ist (WordPress-Quellcode). Geladen wird er im Verwaltungsbereich über die Anmeldeprüfung, die auf dem Heartbeat aufbaut (WordPress-Quellcode). Jedes offene Tab der Verwaltung ist damit ein eigener Taktgeber: Dashboard, Beitragsliste, Editor, Bestellübersicht.
Für vergessene Fenster gibt es eine Bremse. Verliert das Fenster den Fokus, wechselt der Takt auf 120 Sekunden (WordPress-Quellcode), und nach 10 Minuten (WordPress-Quellcode) ohne Aktivität pausiert er ganz. Genau diese Pause schaltet WordPress auf den Bildschirmen zum Bearbeiten und Anlegen von Beiträgen aber ab (WordPress-Quellcode). Dort hört der Takt erst nach 60 Minuten (WordPress-Quellcode) ohne Aktivität auf. Ein Editor, der über die Mittagspause offen bleibt, meldet sich also weiter beim Server.
Jeder Takt ist ein POST an admin-ajax.php. Die Datei lädt den vollständigen Kern, dazu die Verwaltungsfunktionen und alle Ajax-Handler des Kerns, und löst admin_init aus (WordPress-Quellcode). Jede Erweiterung, die sich dort einhängt, läuft bei jedem Takt mit. Ein Team mit mehreren offenen Tabs je Person erzeugt so eine gleichmäßige Grundlast ungecachter Anfragen, die mit dem Besucherverkehr nichts zu tun hat und trotzdem dieselben Worker belegt. Ein ähnliches Muster kennt man aus Shopsystemen, deren Verwaltung im Browser-Tab Hintergrundarbeit erledigt; der Beitrag zum langsamen Shop-Backend beschreibt es.
admin-ajax im Frontend: der Umweg am Cache vorbei
Das Plugin-Handbuch legt fest, dass alle Ajax-Anfragen von WordPress an wp-admin/admin-ajax.php gehen (WordPress Plugin Handbook). Für Erweiterungen und Themes war das lange der einfachste Weg, aus einer Seite heraus Daten nachzuladen. Im Frontend findet er sich an vielen Stellen: Knöpfe zum Nachladen weiterer Beiträge, Filter in Listen, Live-Suche, Aufrufzähler, Formulare, Popups mit wechselndem Inhalt oder ein Warenkorbzähler, der nach jedem Seitenaufruf neu abgefragt wird.
Für den Cache sind diese Aufrufe unbrauchbar. Meist gehen sie per POST hinaus, und die HTTP-Semantik erlaubt nicht, eine POST-Anfrage aus einer gespeicherten POST-Antwort zu bedienen (RFC 9110). Zusätzlich setzt admin-ajax.php selbst Kopfzeilen gegen das Zwischenspeichern (WordPress-Quellcode); seit Version 6.8 (WordPress Code Reference) enthalten sie no-store und private, unabhängig davon, ob jemand angemeldet ist. Selbst ein GET-Aufruf an diesen Endpunkt landet damit bei PHP.
Die Rechnung ist schlicht: Hängt an jeder gecachten Seite ein Ajax-Aufruf, der beim Laden feuert, verursacht jeder Seitenaufruf wieder eine PHP-Anfrage, und zwar eine mit vollem Start des Kerns. Der Seiten-Cache spart das HTML, aber nicht den Worker. Bei Listen, die per Knopf weiterladen, läuft zudem jede Folgeseite einzeln über PHP; welche Bauform sich für lange Listen eignet, vergleicht der Beitrag Seitenzahlen, Mehr laden oder Endlos.
Einen eigenen Blick verdient der Heartbeat im Frontend. Der Kern kennt eigene Filter für nicht angemeldete Besucher (WordPress-Quellcode). Bindet ein Theme oder eine Erweiterung den Heartbeat auf öffentlichen Seiten ein, taktet jedes offene Besucherfenster gegen admin-ajax.php. Ob das passiert, zeigt der Quelltext einer Seite im privaten Fenster: Tauchen dort heartbeat.min.js oder heartbeatSettings auf, läuft der Takt auch für Gäste.
Messen: ungecachte Anfragen je Endpunkt
Bevor etwas umgestellt wird, gehört auf den Tisch, wie groß der Anteil dieser Anfragen ist. Grundlage ist das Access-Log des Webservers über einen typischen Arbeitstag, besser über einen Tag mit Redaktionsbetrieb und laufender Kampagne. Aus ihm lassen sich die Anfragen nach Pfad gruppieren und der Gesamtzahl gegenüberstellen.
- Pfade gruppieren:
/wp-cron.php,/wp-admin/admin-ajax.php,/wp-json/,/wp-login.phpund der Rest. Abfrageparameter vorher abschneiden, sonst zählt jeder Cron-Lauf mit seinem Zeitstempel als eigene Adresse. - Antwortzeit mitschreiben: im Logformat die Bearbeitungsdauer ergänzen, bei nginx
$request_time, bei Apache%D. Erst damit wird aus einer Anzahl eine Last. - Cache-Status mitschreiben: Gibt die Cache-Schicht einen Kopf wie
X-Cacheoder einen Upstream-Status aus, gehört er ins Log. So lässt sich der Anteil ungecachter Anfragen am Gesamtverkehr direkt ablesen. - Die admin-ajax-Aktion erfassen: Bei POST steht der Parameter
actionim Rumpf der Anfrage und damit nicht im Log. Ein kleines Must-use-Plugin schreibt Aktion und Dauer mit, ohne personenbezogene Daten. - Den Worker-Vorrat beobachten: Die Statusseite von PHP-FPM zeigt aktive Prozesse, die Länge der Warteschlange und, ob der Höchstwert an Prozessen je erreicht wurde (PHP-Handbuch).
# Anfragen je Endpunkt aus einem Access-Log im kombinierten Format
awk '{ split($7, p, "?"); print p[1] }' access.log \
| grep -E '^/(wp-cron\.php|wp-admin/admin-ajax\.php|wp-json/|wp-login\.php)' \
| sed -E 's#^/wp-json/.*#/wp-json/#' \
| sort | uniq -c | sort -rn<?php
// Nur zur Messung: schreibt Aktion und Dauer jeder admin-ajax-Anfrage ins PHP-Fehlerprotokoll.
if ( wp_doing_ajax() ) {
add_action( 'shutdown', function () {
$action = isset( $_REQUEST['action'] ) ? sanitize_key( wp_unslash( $_REQUEST['action'] ) ) : '-';
$ms = (int) round( ( microtime( true ) - $_SERVER['REQUEST_TIME_FLOAT'] ) * 1000 );
error_log( sprintf( 'admin-ajax action=%s ms=%d', $action, $ms ) );
} );
}Nach einem Tag ergibt sich ein klares Bild: welcher Anteil der PHP-Anfragen auf Cron, Heartbeat und Frontend-Aktionen entfällt, welche Aktion am häufigsten feuert und wie lange sie dauert. Das Protokoll-Plugin wird danach wieder entfernt. Wo die Zeit innerhalb einer einzelnen Anfrage bleibt, macht der Header aus dem Beitrag zu Server-Timing sichtbar.
| Endpunkt | Auslöser | Im Seiten-Cache? | Gegenmittel |
|---|---|---|---|
/wp-cron.php | Loopback nach einer ungecachten Anfrage | nicht enthalten | System-Cron mit WP-CLI, DISABLE_WP_CRON |
admin-ajax.php, Aktion heartbeat | offene Verwaltungs- und Editorfenster | nicht enthalten | Takt außerhalb des Editors strecken, im Frontend nur bei Bedarf laden |
admin-ajax.php, eigene Aktionen | Theme oder Erweiterung beim Laden oder Klicken | nicht enthalten | ins HTML rendern oder als GET-Route cachebar machen |
/wp-json/ per GET ohne Anmeldung | Skripte im Frontend | möglich | Cache-Control setzen, Cache-Regel für die Route |
| Seiten für Angemeldete | Redaktion, Kundenkonto | nicht enthalten | Grundlast über Object-Cache und schlanke Vorlagen senken |
WP-Cron auf den System-Cron umstellen
Die Dokumentation beschreibt den Weg selbst: den Aufgabenplaner des Systems einrichten und WP-Cron in der wp-config.php abschalten. Der Hinweis dazu ist deutlich. Ohne diesen Schritt würde WordPress WP-Cron weiter bei jedem Seitenaufruf ausführen; das sei dann nicht mehr nötig und trage zu zusätzlicher Last auf dem Server bei (WordPress Plugin Handbook).
Für den Aufruf gibt es zwei Wege. Das Handbuch zeigt den Abruf von wp-cron.php per HTTP, etwa mit wget. Das funktioniert überall, läuft aber wieder durch Webserver und PHP-FPM und belegt dort einen Worker. Wo WP-CLI installiert ist, eignet sich wp cron event run --due-now besser: Der Befehl führt alle fälligen Ereignisse aus und beachtet die Sperre gegen überlappende Läufe (WP-CLI-Dokumentation). Er läuft als eigener PHP-Prozess auf der Kommandozeile, also außerhalb des Worker-Vorrats, und ist nicht an die Zeitgrenzen von Webserver und Proxy gebunden.
# wp-config.php, oberhalb der Zeile "That's all, stop editing!"
define( 'DISABLE_WP_CRON', true );
# crontab des Benutzers, unter dem WordPress läuft (crontab -e)
* * * * * cd /var/www/example/htdocs && wp cron event run --due-now --quiet >> /var/www/example/logs/wp-cron.log 2>&1Der Takt richtet sich nach der feinsten Planung, die Sie brauchen. Das Handbuch zeigt als Beispiel einen Aufruf alle 15 Minuten (WordPress Plugin Handbook). Für Wartungsaufgaben reicht das, für einen Beitrag, der zu einer bestimmten Minute erscheinen soll, kann es bis zu einer Viertelstunde Verspätung bedeuten. Der Eintrag oben läuft deshalb jede Minute. Jeder Lauf startet WordPress einmal auf der Kommandozeile und endet, wenn nichts fällig ist; er konkurriert dabei nicht mit Besucheranfragen um Worker, wohl aber um Rechenzeit und Datenbank.
Zwei Dinge gehören zur Umstellung. Erstens schreibt der Cron-Eintrag seine Ausgabe in eine Logdatei und nicht ins Leere; nur so fällt ein dauerhaft scheiternder Lauf auf, bevor ein geplanter Beitrag fehlt. Zweitens wird geprüft, ob die Umstellung wirkt: wp cron event list zeigt die anstehenden Ereignisse mit ihrem nächsten Termin, und im Access-Log sollten Aufrufe von wp-cron.php verschwinden. Bleiben sie, ruft noch ein externer Dienst oder ein alter Cron-Eintrag die Datei auf.
ALTERNATE_WP_CRON ist kein Ersatz
ALTERNATE_WP_CRON leitet den Browser des Besuchers um, damit der Lauf in der abgebrochenen Verbindung weiterarbeitet. Sie entlastet keinen Worker, und die Dokumentation nennt selbst Risiken dieses Verfahrens (WordPress Advanced Administration Handbook). Für das hier beschriebene Problem ist der System-Cron der passende Weg.Den Heartbeat drosseln, ohne Sperren zu verlieren
Abschalten ist beim Heartbeat die falsche Richtung. Ohne ihn verliert der Editor die Bearbeitungssperre, die verhindert, dass zwei Personen denselben Beitrag gleichzeitig überschreiben, und die Warnung vor einer abgelaufenen Anmeldung. Der passende Hebel ist der Filter heartbeat_settings und darin der Wert minimalInterval. Er legt den kürzesten zulässigen Abstand zwischen zwei Takten fest und übersteuert alle kürzeren Werte.
Die Grenzen stehen im Quellcode: Der Mindestabstand darf bis zu 600 Sekunden betragen, doch alles über 120 Sekunden schränkt Funktionen wie die Bearbeitungssperre ein oder schaltet sie ab (WordPress-Quellcode). Der Grund ist die Laufzeit der Sperre selbst, die nach 150 Sekunden (WordPress-Quellcode) ohne Signal abläuft. Für die Bildschirme zum Bearbeiten von Beiträgen bleibt es deshalb beim Standard. Auf Dashboard, Listen und Einstellungsseiten lässt sich der Takt bis zu dieser Grenze strecken; im Frontend wird der Heartbeat für Gäste nur geladen, wenn eine Funktion ihn tatsächlich braucht.
<?php
add_filter( 'heartbeat_settings', function ( $settings ) {
global $pagenow;
// Im Editor bleibt der Standard, sonst höchstens ein Takt alle 120 Sekunden.
if ( 'post.php' !== $pagenow && 'post-new.php' !== $pagenow ) {
$settings['minimalInterval'] = 120;
}
return $settings;
} );
// Im Frontend für Gäste nicht laden, sofern keine Funktion den Takt braucht.
add_action( 'wp_enqueue_scripts', function () {
if ( ! is_user_logged_in() ) {
wp_deregister_script( 'heartbeat' );
}
}, 100 );Nach dem Einspielen werden die Funktionen geprüft, die am Heartbeat hängen: Hinweis auf parallele Bearbeitung im Editor, Warnung vor dem Ablauf der Anmeldung und alles, was eine Erweiterung im Frontend über den Takt löst. Danach gilt dieselbe Messung wie vorher: Die Zahl der admin-ajax-Anfragen mit der Aktion heartbeat sinkt. Organisatorisch hilft eine einfache Absprache ebenso: Wer nicht schreibt, schließt den Editor. Vor einem Aktionsstart mit vielen Beteiligten ist das ein günstiger Hebel.
Frontend-Aufrufe auf cachebare Wege umstellen
Die Liste der admin-ajax-Aktionen aus der Messung ist der Arbeitsplan. Jede Aktion fällt in eine von drei Gruppen, und für jede gibt es einen Weg, der den Cache wieder ins Spiel bringt.
Für alle gleich: ins HTML
Liefert die Aktion für alle Besucher dasselbe, etwa ähnliche Beiträge, Filteroptionen oder einen Teaser, gehört der Inhalt direkt in die Seite. Er wird dann mit dem HTML gecacht und kostet beim Aufruf keine eigene Anfrage.
Nachladen: als GET-Route
Muss nachgeladen werden, etwa die zweite Seite einer Liste, eignet sich eine GET-Route der REST-API. Für nicht angemeldete Anfragen sendet WordPress dort ab Werk keine No-Cache-Köpfe (WordPress-Quellcode); mit passendem Cache-Control kann eine Cache-Schicht die Antwort speichern.
Persönlich: selten und klein
Warenkorbzähler oder Anmeldestatus unterscheiden sich je Person und lassen sich oft aus einem Cookie im Browser lesen. Wo der Server gefragt werden muss, dann bei Bedarf, etwa beim Öffnen des Warenkorbs, und nicht bei jedem Seitenaufruf.
Ein Stolperstein bei cachebaren Seiten sind Nonces, die Sicherheitsmerkmale von WordPress gegen untergeschobene Anfragen. Ein Nonce lässt sich laut Handbuch innerhalb von 24 Stunden (WordPress Plugin Handbook) beliebig oft verwenden, und nach 12 Stunden (WordPress Plugin Handbook) erzeugt WordPress eine neue Zahl. Steckt ein Nonce im gecachten HTML, scheitern die Aufrufe, sobald die Seite länger im Cache liegt, als das Merkmal gilt. Entweder bleibt die Cache-Lebensdauer solcher Seiten darunter, oder das Merkmal wird erst bei Bedarf geholt, zusammen mit der Aktion, die es braucht.
Bei Zählern für Aufrufe oder Klicks lohnt die Frage, ob sie im Moment des Seitenaufrufs geschrieben werden müssen. Häufig reicht es, die Zahlen aus dem Access-Log oder gesammelt in größeren Abständen zu übernehmen. Jede Aktion, die so entfällt, gibt dem Seiten-Cache seine Wirkung zurück. Wie die Schichten von Browser-Cache über Seiten-Cache bis zum Object-Cache zusammenspielen, erläutert die Seite zu Caching-Strategien.
Vorher und nachher messen
Die Wirkung zeigt sich nicht im Lighthouse-Wert der Startseite, die ohnehin aus dem Cache kommt. Gemessen wird dort, wo die Last lag: Anteil der ungecachten PHP-Anfragen je Endpunkt, Zahl und Dauer der admin-ajax-Aktionen, aktive Prozesse und Warteschlange auf der FPM-Statusseite sowie der TTFB der Seiten, die nicht aus dem Cache kommen können, etwa Warenkorb, Suche und Kundenkonto. Für den Vergleich braucht es zwei Zeitfenster mit gleichem Profil: derselbe Wochentag, dieselbe Tageszeit, vergleichbarer Redaktionsbetrieb.
Ein Zähler auf der Statusseite verdient besondere Aufmerksamkeit: max children reached gibt an, ob der Höchstwert an Prozessen erreicht wurde (PHP-Handbuch). Wächst er nach der Umstellung nicht mehr, hat der Worker-Vorrat wieder Luft. Ob diese Luft auch unter Spitzenlast reicht, klärt ein Lasttest vor der Hochsaison; wie TTFB-Werte einzuordnen sind, beschreibt der Grundlagenbeitrag zur Server-Antwortzeit. Bleibt die Grundlast trotz allem hoch, liegt die Ursache oft eine Schicht tiefer, in PHP-Konfiguration, Datenbank oder Hosting; diese Ebene behandelt die Server-Optimierung.
Vor der Black Week: geplante Aktionen pünktlich freischalten
Ende November treffen beide Seiten des Problems zusammen. Aktionsseiten und Beiträge werden Tage vorher angelegt und auf Mitternacht geplant, gleichzeitig arbeiten mehr Personen als sonst in der Verwaltung. Mit WP-Cron hängt die Veröffentlichung am ersten ungecachten Aufruf nach Mitternacht. Auf einer Website, deren Startseite und Kategorien vollständig aus dem Cache kommen, kann das dauern; die Terminfehler, die das Handbuch beschreibt, treffen dann genau die Nacht, auf die alles hinarbeitet.
Mit einem System-Cron im Minutentakt erscheint der Beitrag pünktlich. Danach müssen die betroffenen Seiten aus dem Cache fallen und neu aufgewärmt werden, sonst sehen Besucher die alte Startseite. Wie sich das für die Nacht planen lässt, beschreibt der Beitrag Aktionsstart um Mitternacht: Cache vorwärmen.
- System-Cron eingerichtet,
DISABLE_WP_CRONgesetzt und das Log des Cron-Eintrags eine Woche lang ohne Fehler - Ein Testbeitrag eine Woche vor dem Start auf eine feste Uhrzeit geplant und pünktlich erschienen
- Heartbeat außerhalb des Editors gestreckt und im Frontend für Gäste nur geladen, wo eine Funktion ihn braucht
- Frontend-Aktionen aus der Messung umgebaut oder mit Begründung beibehalten
- FPM-Statusseite für die Nacht im Blick,
max children reachedvor dem Start notiert - Absprache in der Redaktion: Editorfenster schließen, wenn niemand schreibt
Wer diese Liste für die eigene Installation abarbeiten lassen möchte, findet den Ablauf aus Messung, Umstellung und Nachmessung auf der Seite zur WordPress-Performance; die Messung allein ist Teil der Performance-Analyse.
Quellen und Dokumentation
wp cron event run --due-now aufruft und seine Ausgabe in eine Logdatei schreibt, ist es der Weg, den das Plugin-Handbuch selbst beschreibt.minimalInterval zu strecken und ihn im Frontend für Gäste nur zu laden, wo eine Funktion ihn braucht.admin-ajax.php Kopfzeilen gegen das Zwischenspeichern und startet für jede Anfrage den vollständigen Kern samt Verwaltungsfunktionen. Abhilfe schaffen Inhalte im HTML, GET-Routen der REST-API mit Cache-Control oder Aufrufe erst bei Bedarf.max children reached ist größer als null (PHP-Handbuch). Wie sich die Server-Antwortzeit danach gezielt senken lässt, zeigt die Seite zur TTFB-Optimierung.Verwandte Artikel
PHP-Laufzeit optimieren: OPcache, JIT und FPM-Worker
Zwischen Webserver und Datenbank liegt die PHP-Laufzeit. Wie OPcache, Preloading, JIT und die FPM-Worker die Antwortzeit unter Last bestimmen.
Early Hints 103: die Serverdenkzeit sinnvoll nutzen
Statuscode 103 nach RFC 8297: Wie der Server schon während der Denkzeit preconnect- und preload-Hinweise sendet, plus Umsetzung in nginx und ehrliche Grenzen.
KI-Crawler-Last: Wenn Bots den Server ausbremsen
Bots erzeugen über die Hälfte der HTML-Anfragen. Warum KI-Crawler uncachebare Long-Tail-URLs treffen, wie Sie die Last messen und drosseln statt blocken.