WP Manifestindependent plugin directory
manifest / performance / wdt-404-cache

WDT 404 Cache

Cacht die gerenderte 404-Seite für Gäste kurz als Datei und liefert sie aus, bevor WooCommerce, Theme und Hauptabfrage laden. Gegen Bots, die immer wieder dieselben nicht existierenden URLs abfragen.

★ 0stars

Install

Download source zip No release zip yet

This is the repository archive. It installs, but the folder name will carry the branch suffix and updates will not flow.

Install straight from GitHub with WP-CLI:

wp plugin install https://github.com/webdevtrust/wdt-404-cache/archive/refs/heads/main.zip
No built-in updater. New versions won't appear under Dashboard → Updates. Install new releases yourself, or let Git Updater track the repository.

Cacht die gerenderte WordPress-404-Seite für Gäste kurz als Datei und liefert sie aus, bevor WooCommerce, das Theme und die Hauptabfrage laden.

Anlass: Bots fragen rund um die Uhr immer wieder dieselben, noch nicht veröffentlichten Produkt-URLs ab. Page-Caches und CDNs (z. B. Servebolt ACD) cachen 404-Antworten in der Regel nicht. Deshalb rendert PHP jeden Aufruf komplett (0,5–0,7 s). Das Plugin wirkt unabhängig von IP, CDN und Hoster.

Status: auf Staging getestet (WooCommerce-Shop auf Servebolt, Oktober 2026), siehe Staging-Ergebnisse.

Funktionsweise

  1. Speichern: Ist ein Aufruf eine 404 und cachebar (siehe unten), wird das fertige HTML samt Response-Headern (ohne Set-Cookie) komprimiert unter wp-content/cache/wdt-404-cache/<sha1>.cache abgelegt. Gespeichert wird nur, wenn die Antwort wirklich Status 404 hat, text/html ist, mit </html> endet, kein DONOTCACHEPAGE gesetzt ist und kein Login- oder Warenkorb-Cookie gesetzt wird.
  2. Ausliefern: Beim nächsten Aufruf derselben URL liefert das Plugin den Eintrag schon beim Laden seiner Hauptdatei aus: Status 404, gespeicherte Header plus X-WDT-404-Cache: HIT; age=<Sekunden>. Danach ist Schluss. WooCommerce, Germanized, Theme, Übersetzungen und Hauptabfrage laufen dann nicht. Plugins werden alphabetisch geladen, Plugins mit Namen vor wdt-… (z. B. Mollie, Rank Math, Servebolt) also noch eingebunden. Mit dem optionalen MU-Loader entfällt auch das.

Wann gecacht wird

  • GET oder HEAD, normaler Theme-Aufruf (index.php), kein Admin, AJAX, REST, Cron, WP-CLI oder XML-RPC
  • kein Cookie, dessen Name beginnt mit wordpress_logged_in_, wordpress_sec_, wp-postpass_, comment_author_, woocommerce_items_in_cart, woocommerce_cart_hash, wp_woocommerce_session_ oder woocommerce_recently_viewed
  • keine Query-Parameter, außer ignorierten Tracking- und Cache-Buster-Parametern: _, utm_*, gclid, gbraid, wbraid, fbclid, msclkid, mc_cid, mc_eid, _ga, _gl. /produkt/x/?_=123 nutzt also denselben Eintrag wie /produkt/x/. Jeder andere Parameter (z. B. ?p=5) schaltet den Cache für diesen Aufruf ab.

Schlüssel: Schema + Host + Pfad.

Wann der Cache geleert wird

Komplett und sofort (und zur Sicherheit noch einmal am Ende des Requests):

  • ein öffentlich sichtbarer Beitrag, eine Seite oder ein Produkt wird veröffentlicht, aktualisiert oder zurückgezogen (auch geplante Veröffentlichungen und Slug-Änderungen)
  • Begriffe (Kategorien, Tags, Attribute) werden angelegt oder bearbeitet
  • Rewrite-Regeln werden neu erzeugt (z. B. Permalinks speichern)
  • Menüs oder Customizer werden gespeichert, Theme-Wechsel, Plugins (de)aktivieren oder aktualisieren
  • Link „Cache leeren“ auf der Plugins-Seite
  • do_action('wdt_404_cache_flush'); aus eigenem Code

Alles andere deckt die TTL ab, z. B. eine neue Weiterleitung in Rank Math oder Redirection. Dort gilt also bis zu TTL Sekunden noch die alte 404. Bei Bedarf den Cache von Hand leeren.

Schutz vor Überlauf

Höchstens WDT_404_CACHE_MAX_ENTRIES Dateien (Standard 500). Ist das Limit erreicht, werden zuerst abgelaufene Einträge gelöscht. Ist danach noch kein Platz, wird nichts Neues gespeichert. Zufällige Angriffspfade können das Verzeichnis also nicht volllaufen lassen. Bodies werden mit zlib komprimiert.

Konfiguration (wp-config.php, alles optional)

define('WDT_404_CACHE_TTL', 300);           // Sekunden, 1–3600, Standard 300
define('WDT_404_CACHE_MAX_ENTRIES', 500);   // Standard 500
define('WDT_404_CACHE_DIR', '/pfad/');      // Standard wp-content/cache/wdt-404-cache/
define('WDT_404_CACHE_DISABLED', true);     // Notschalter: nichts ausliefern, nichts speichern

TTL: Weil der Cache beim Veröffentlichen sofort geleert wird, ist eine längere TTL unkritisch. Bei einer URL, die alle 10 s abgefragt wird, rendert PHP pro TTL-Fenster einmal: Bei 60 s werden etwa 83 % der Aufrufe abgefangen, bei 300 s etwa 97 %.

Filter

Die Filter werden beim Ausliefern gelesen, also vor den normalen Plugins. Deshalb müssen sie aus einem MU-Plugin kommen (bzw. aus dem MU-Loader oder einem alphabetisch früheren MU-Plugin).

// weitere Cookies, die das Caching verhindern (Präfix-Vergleich)
add_filter('wdt_404_cache_bypass_cookies', fn (array $p) => [...$p, 'wcml_client_currency']);

// weitere ignorierbare Query-Parameter ("prefix_*" = Präfix)
add_filter('wdt_404_cache_ignored_query_args', fn (array $a) => [...$a, 'ref']);

Optional: MU-Loader

examples/mu-plugins/wdt-404-cache-loader.php nach wp-content/mu-plugins/ kopieren. Dann wird ausgeliefert, bevor überhaupt ein normales Plugin geladen ist. Der Loader wirkt nur, solange das Plugin aktiv ist. Deaktivieren auf der Plugins-Seite schaltet also beides ab.

Status auf der Plugins-Seite

Unter der Beschreibung steht eine Statuszeile, z. B.:

✅ aktiv · 3 gültige Einträge (max. 500) · TTL 300 s · Auslieferung früh (MU-Loader) · zuletzt geleert 12.10.2025 14:03

Daneben gibt es den Link „Cache leeren“ (nur mit manage_options).

Einschränkungen

  • Ausgeliefert wird das HTML so, wie es das Theme gerendert hat. Plugins, die ihren eigenen Output-Buffer vor template_redirect starten (manche Minifier/Optimierer), sind im gecachten HTML nicht enthalten. Für eine 404-Seite ist das normalerweise nur ein kosmetischer Unterschied.
  • Inhalte, die sich nur nach User-Agent, Geolocation oder anderen hier nicht erfassten Cookies unterscheiden, werden nicht getrennt gecacht. Bei Bedarf das Cookie über wdt_404_cache_bypass_cookies ergänzen.
  • Bei mehreren Webservern ohne gemeinsames Dateisystem hat jeder Server seinen eigenen Cache. Das funktioniert, beim Leeren über das Backend wird aber nur der Cache des Servers geleert, der den Admin-Request bearbeitet. Die TTL begrenzt das.

Tests

  1. Als Gast (ohne Cookies) eine nicht existierende URL zweimal abrufen:
    curl -sI https://shop.example/produkt/gibt-es-nicht/ | grep -iE '^(HTTP|x-wdt)'
    curl -sI https://shop.example/produkt/gibt-es-nicht/ | grep -iE '^(HTTP|x-wdt)'

    Der erste Aufruf liefert 404 ohne HIT-Header, der zweite 404 mit X-WDT-404-Cache: HIT. Der HTML-Body ist identisch (curl -s … | md5sum).

  2. …/?_=123 liefert ebenfalls HIT, …/?foo=1 nicht.
  3. Mit Warenkorb-Cookie (-H 'Cookie: woocommerce_items_in_cart=1') oder eingeloggt gibt es keinen HIT.
  4. Einen Entwurf mit passendem Slug veröffentlichen: Die URL liefert sofort 200.
  5. Antwortzeit vorher/nachher: curl -so /dev/null -w '%{time_starttransfer}\n' …

Staging-Ergebnisse

Gemessen per curl von außen (TTFB = Antwortzeit bis zum ersten Byte) und mit dem Mess-MU-Plugin aus wdt-third-party-performance (PHP-Zeit, Queries):

Anfrage TTFB PHP-Zeit Queries
404 ohne Cache (z. B. ?foo=1, Warenkorb-Cookie) ~0,7–0,8 s ~630–700 ms ~325–330
404-HIT ohne MU-Loader ~0,13–0,15 s ~50–70 ms 23–28
404-HIT mit MU-Loader ~0,09 s geschätzt 5–10 ms¹ –
statische Datei (nur Netzwerk, Vergleichswert) ~0,08 s – –

¹ Mit MU-Loader beendet das Plugin den Aufruf, bevor das Mess-Plugin lädt. Geschätzt aus dem Abstand zur statischen Datei.

Geprüft:

  • Das ausgelieferte HTML ist identisch mit dem gerenderten; Status 404 und Cache-Control bleiben erhalten.
  • ?_=… und ?utm_* nutzen den Eintrag, andere Parameter und Warenkorb-Cookies nicht. HEAD funktioniert.
  • Nach dem Veröffentlichen eines Entwurfs mit dem gecachten Slug liefert die URL sofort 200.

Dateien

wdt-404-cache.php                           Bootstrap: frühe Auslieferung, dann Speichern/Leeren registrieren
includes/NotFoundCache.php                  Cache: Ausliefern, Speichern, Invalidierung
includes/Admin.php                          Statuszeile, „Cache leeren“ auf der Plugins-Seite
uninstall.php                               Löscht das Cache-Verzeichnis (keine DB-Daten)
examples/mu-plugins/wdt-404-cache-loader.php  Optionaler MU-Loader

Anforderungen

PHP 8.2+, WordPress. WooCommerce ist nicht nötig. Die Cookie-Liste berücksichtigt WooCommerce aber.

Lizenz

GPL-2.0-or-later, siehe LICENSE.