Öffnet in einem neuen Tab

Warum leitet WordPress von alleine URLs um, wenn Permalinks aktiv sind?

Symptom:

Wir hatten diesen Fall bei einer Kundenwebsite:

Eine URL wie /gutscheine/ landete per 301 auf /xxx/gutscheine-fuer-gesangs-workshop/.

  1. Es war Kein Redirect-Plugin aktiv,
  2. in der .htaccess und der nginx-Konfiguration standen keine Redirects nichts.
  3. Alle Plugins waren deaktivert
  4. die Weiterleitung blieb hartnäckig bestehen.

Stellt man die Permalinks auf „Einfach“, verschwindet sie.

Der HTTP-Header deutet klar auf WordPress als Verursacher hin:

curl -skI https://example.test/gutscheine/Code-Sprache: JavaScript (javascript)

liefert:

HTTP/2 301
location: https://example.test/singen-lernen-shop/gutscheine-fuer-gesangs-workshop/
x-redirect-by: WordPressCode-Sprache: HTTP (http)

x-redirect-by: WordPress bedeutet: Die Weiterleitung kommt direkt von WordPress.

Direkt abschalten hier…

Die Ursache: WordPress als “Wahrsager”

Man lernt bei WordPress nie aus…

Findet WordPress zu einer URL keinen Inhalt, entsteht ein 404. Bevor dieser ausgeliefert wird, ruft der Core die Funktion

redirect_guess_404_permalink()

auf.

Diese Funktion sucht in der Datenbank nach einem veröffentlichten Beitrag, dessen Slug mit der Query Variablen beginnt.

Sinngemäß wird so etwas gemacht:

SELECT ID FROM wp_posts
WHERE post_name LIKE 'gutscheine%'
  AND post_status = 'publish'
  AND post_type IN (/* öffentliche Post-Types */);Code-Sprache: JavaScript (javascript)

Gibt es einen Treffer, leitet WordPress per 301 dorthin um. Der Slug gutscheine-fuer-gesangs-workshop beginnt mit gutscheine.

Die Weiterleitung wird nirgendwo gespeichert. WordPress berechnet sie bei jedem Seitenaufruf neu. Deshalb kann man sich hier dumm und dämlich suchen.

Der Guess Redirect funktioniert nur, wenn die Permalink Struktur anders als “Einfach” eingestellt ist. Die Rewrite-Regeln bei komplexen Permalinks machen aus /gutscheine/ die Abfrage-Variable “name=gutscheine” und diese wird dann in der Datenbankabfrage benutzt. Bei Einfach werden die Post-IDs benutzt und es gibt keine Abfrage-Variable.

Das Problem sind nicht deterministische Vorhersagen!

Wahrscheinlichkeiten anstelle von exakten Abläufen ist ja in Zeiten von LLMs ein Trend aber eben auch super nervig, wenn dann Weiterleitungen passieren, die eigentlich nicht passieren sollen und man es einfach nicht so hinnehmen möchte.

Die Abfrage hat darüber hinaus keine Sortierung. Gibt es mehrere passende Beiträge, entscheidet die Datenbank, welcher zuerst kommt. Das bedeutet, im Laufe der Lebenszeit einer Website kann sich die Weiterleitung mal einfach so intern ändern.

Aus unserer Sicht ein typisches Beispiel von “Gut gemeint ist das Gegenteil von Gut”. Wenn ich mich in der URL vertippe möchte ich eine konkrete Fehlermeldung 404 Seite nicht gefunden und keine Weiterleitung auf die nächstbeste Seite.

Wenn man das Ganze dann auch noch debuggen muss, ist es super nervig und wir haben wirklich lange gebraucht, bis hier der Groschen gefallen war. :-/

Was es noch gibt: Den “Old-Slug-Redirect”

WordPress hat übrigens einen zweiten, ähnlichen Mechanismus.

Ändert man den Slug eines Beitrags, speichert WordPress den alten in wp_postmeta unter _wp_old_slug und leitet die alte URL weiter. Dieser Redirect ist ebenfalls implizit, aber vergleicht wenigstens exakt, nicht per Teilstring. Prüfen lässt sich das Ganze so:

wp db query "SELECT post_id, meta_value FROM $(wp db prefix)postmeta WHERE meta_key = '_wp_old_slug' AND meta_value = 'gutscheine';"Code-Sprache: JavaScript (javascript)

Für Leute, die vor dem gleichen Problem stehen: Hier die Diagnose in drei Schritten

  1. Header prüfen. Steht dort x-redirect-by: WordPress, ist der Core verantwortlich. Wenn nicht, irgendein PlugIn oder es steht direkt in der .htaccess.
  2. Old-Slug ausschließen mit der Abfrage oben.
  3. Raten testweise abschalten (siehe unten) und erneut curl ausführen.
  4. Kommt jetzt ein 404, ist der Guess-Redirect bestätigt.

So schaltet man den Redirect Wahrsager von WordPress ab:

<?php
// Raten komplett abschalten
add_filter('do_redirect_guess_404_permalink', '__return_false');Code-Sprache: HTML, XML (xml)

Oder, wenn man nur exakte Treffer erlauben möchte:

<?php
// Nur umleiten, wenn der Slug exakt übereinstimmt
add_filter('strict_redirect_guess_404_permalink', '__return_true');Code-Sprache: HTML, XML (xml)