WordPress REST API: 401 nach Nginx-Migration beheben
Nach dem Umzug auf Nginx antwortet die WordPress REST API mit 401? Die Ursache ist ein 301-Redirect, bei dem der Authorization-Header verloren geht. Mit Fix.
Inhalt
Alles lief, monatelang. Dann kam der Serverumzug von Apache auf Nginx und plötzlich antwortete die WordPress REST API mit 401 Unauthorized auf Anfragen, die vorher problemlos funktioniert hatten. Gleicher Code, gleiche Zugangsdaten, gleiche WordPress-Installation, anderer Webserver.
Diese Sorte Fehler beschäftigt einen eine Weile, weil die naheliegende Antwort „irgendwas am Server ist falsch konfiguriert“ zwar stimmt, aber auf eine Art, die man nicht sofort sieht. Die debug.log schweigt. WordPress selbst meldet keinen Fehler. Wenn du nicht weißt, wo du hinschauen musst, kannst du lange auf Konfigurationsdateien starren.
Hier ist der Weg, der bei mir zur Ursache geführt hat, was technisch passiert und warum der Fix so einfach ist, sobald man ihn kennt.
Das Setup
Eine externe Anwendung ruft die WordPress REST API auf. Bei mir war es ein n8n-Workflow, es könnte genauso eine Mobile App oder ein eigenes Script sein. Die Authentifizierung läuft über einen Authorization-Header, so wie die API es erwartet. Auf Apache: 200, sauber. Nach dem Umzug auf Nginx: 401, jedes Mal.
Die erste Reaktion ist meistens eine Runde durch die WordPress-Einstellungen. REST API aktiv? Application Password korrekt? Benutzerrechte in Ordnung? Alles ja. Dann die Nginx-Konfiguration: Wirft irgendein Rewrite die Header weg? Nichts Auffälliges. Dann beginnt das ratlose Googeln.
Die Ursache sitzt an einer Stelle, die weder WordPress noch Nginx direkt betrifft. Sie liegt darin, wie HTTP-Clients mit Redirects umgehen und dass Apache und Nginx an genau diesem Punkt unterschiedlich arbeiten.
Der Redirect, den niemand auf dem Schirm hat
Die meisten Domains sind so eingerichtet, dass domain.de auf www.domain.de weiterleitet oder umgekehrt. Das ist Standard, gut für SEO und kaum jemand denkt darüber nach. Auf Apache passiert dieser Redirect über eine RewriteRule in der .htaccess, auf Nginx über einen server-Block mit return 301.
Der Knackpunkt ist, was dabei mit dem Authorization-Header passiert. HTTP-Clients, ob curl, n8n oder dein eigener Code, folgen einem 301 und schicken dabei standardmäßig keinen Authorization-Header an die neue Adresse. Die ursprüngliche Anfrage ging an meinshop.de, der Redirect zeigt auf www.meinshop.de und der Client entscheidet: andere Adresse, anderer Kontext, die Zugangsdaten bleiben hier. Aus Sicherheitssicht ist das vernünftig, im Moment des Umzugs ist es eine Falle.
Auf Apache hat das nie gestört, weil Apache den Redirect je nach Konfiguration intern aufgelöst hat, ohne den Client zu beteiligen, oder so eingestellt war, dass der Header beim Weiterleiten erhalten blieb. Nginx macht das anders. Nginx schickt dem Client seinen 301, der Client folgt ohne Authorization und WordPress verweigert den Zutritt. Alle Beteiligten verhalten sich korrekt und das Ergebnis ist trotzdem ein 401.
Der Nachweis mit curl
Dieser Fehler lässt sich mit zwei curl-Aufrufen vollständig beweisen. Keine Logdateien, kein Plugin-Ausschlussverfahren, nur curl mit -v, das den kompletten Ablauf von Anfrage und Antwort zeigt.
Schritt 1: Anfrage an die Domain ohne www
curl -v -H "Authorization: Bearer dein-application-password" \
https://meinshop.de/wp-json/wp/v2/posts
In der Ausgabe siehst du:
> GET /wp-json/wp/v2/posts HTTP/2
> Host: meinshop.de
> Authorization: Bearer dein-application-password
>
< HTTP/2 301
< location: https://www.meinshop.de/wp-json/wp/v2/posts
Da ist der 301 nach www.meinshop.de. Ohne das Flag -L folgt curl dem Redirect nicht, es macht genau eine Anfrage und zeigt das Ergebnis. Die Anfrage kam mit Authorization an und bekam nur eine Weiterleitung zurück.
Schritt 2: Dem Redirect folgen
curl -v -L -H "Authorization: Bearer dein-application-password" \
https://meinshop.de/wp-json/wp/v2/posts
Mit -L folgt curl dem Redirect und jetzt steht das Problem schwarz auf weiß da:
> GET /wp-json/wp/v2/posts HTTP/2
> Host: meinshop.de
> Authorization: Bearer dein-application-password
>
< HTTP/2 301
< location: https://www.meinshop.de/wp-json/wp/v2/posts
> GET /wp-json/wp/v2/posts HTTP/2
> Host: www.meinshop.de
>
< HTTP/2 401
Im ersten Request ist Authorization: Bearer ... dabei, im zweiten fehlt er. curl hat den Header beim Folgen des Redirects stillschweigend fallen gelassen, genau wie deine Anwendung es tut, und WordPress antwortet folgerichtig mit 401, weil keine Zugangsdaten ankommen.
Schritt 3: Direkt auf www
curl -v -H "Authorization: Bearer dein-application-password" \
https://www.meinshop.de/wp-json/wp/v2/posts
> GET /wp-json/wp/v2/posts HTTP/2
> Host: www.meinshop.de
> Authorization: Bearer dein-application-password
>
< HTTP/2 200
- Der Header kommt an, WordPress ist zufrieden, die API antwortet. Damit ist klar: Das Problem sitzt in der Basis-URL, die deine Anwendung verwendet.
Der Fix: Basis-URL auf die Zieldomain setzen
Deine Anwendung darf keine URL aufrufen, die zu einem Redirect führt. Sie muss direkt dorthin, wo WordPress tatsächlich erreichbar ist. Leitet deine Domain auf www.meinshop.de weiter, ist https://www.meinshop.de/wp-json/ deine Basis-URL, und zwar ausschließlich diese. Die Weiterleitung ist für Browser gedacht, dein API-Client hat davon nichts.
In PHP:
$base_url = 'https://www.meinshop.de/wp-json/wp/v2/';
$response = wp_remote_get( $base_url . 'posts', [
'headers' => [
'Authorization' => 'Bearer ' . $application_password,
],
] );
In JavaScript oder Node:
const BASE_URL = 'https://www.meinshop.de/wp-json/wp/v2';
const response = await fetch(`${BASE_URL}/posts`, {
headers: {
'Authorization': `Bearer ${applicationPassword}`,
},
});
Kein Redirect, kein verlorener Header, kein 401.
Warum Apache das versteckt hat
Wenn du verstehen willst, was da technisch passiert ist, statt nur den Fix anzuwenden, lohnt sich dieser Abschnitt.
Apache hat den Redirect je nach Konfiguration intern aufgelöst, ohne dem Client einen 301 zu schicken. Lief der Redirect über mod_rewrite in der .htaccess und WordPress selbst bereits auf der richtigen Domain, kam beim Client oft gar kein echter HTTP-Redirect an. Apache hat die Anfrage intern weitergereicht und der Header blieb erhalten, weil er nie den Besitzer gewechselt hat.
Vorbildlich ist das nicht, es verschleiert, was passiert. Nginx hält sich enger an die RFC: Ein 301 ist ein 301, der Client bekommt ihn und entscheidet, was er damit macht. Dass HTTP-Clients dabei keine Zugangsdaten weiterreichen, ist ebenfalls kein Fehler von Nginx, sondern gewolltes Verhalten. Du willst nicht, dass dein Browser deinen Authorization-Header an eine fremde Domain schickt, nur weil irgendein Server einen Redirect gesetzt hat.
Konkret heißt das: Beim Wechsel von Apache auf Nginx werden Dinge sichtbar, die vorher still unter der Haube liefen. Das ist manchmal anstrengend, aber auch eine gute Gelegenheit, Konfiguration aufzuräumen, die schon lange so hätte aussehen sollen.
Zwei Dinge, die du bei der Gelegenheit prüfen solltest
Solange curl offen ist, mache ich bei Nginx-Umzügen regelmäßig noch zwei Kontrollen.
Die erste betrifft HTTP und HTTPS. Verwendet deine Anwendung noch http:// und leitet Nginx per 301 auf HTTPS weiter, hast du dieselbe Mechanik und dieselbe Lösung: direkt https:// in die Basis-URL. Prüfen kannst du das so:
curl -v http://www.meinshop.de/wp-json/wp/v2/posts
Kommt ein 301 auf https://, korrigierst du die Basis-URL in der Anwendung.
Die zweite betrifft den Header in der Nginx-Konfiguration. Seltener, aber es kommt vor, wirft Nginx den Authorization-Header aktiv weg, weil eine Direktive das tut oder FastCGI ihn nicht weitergibt. Das erkennst du daran, dass auch der direkte Aufruf auf www.meinshop.de aus Schritt 3 einen 401 liefert. Dann liegt es an der Konfiguration. Prüfe, ob in deinem PHP-FPM-Setup oder in der Site-Konfiguration diese Zeile steht:
fastcgi_param HTTP_AUTHORIZATION $http_authorization;
Fehlt sie, gehört sie meist in den Block mit den fastcgi_params. Wenn du dir unsicher bist, lieber zweimal hinschauen, Nginx verzeiht Konfigurationsfehler nicht großzügig.
Was bleibt
Der 401 kam weder von WordPress noch von Nginx. Er kam daher, dass die Basis-URL der Anwendung auf eine Domain zeigte, die zu einem 301 führt, und HTTP-Clients den Authorization-Header beim Folgen eines Redirects standardmäßig weglassen. Apache hatte das jahrelang verdeckt, Nginx legte es offen.
Der Nachweis: curl -v auf die URL ohne www zeigt den 301, curl -v -L auf dieselbe URL zeigt, wie der Header beim Folgen verschwindet, curl -v direkt auf die Zieladresse zeigt den 200. Der Fix: Basis-URL in der Anwendung auf die Domain setzen, auf der WordPress wirklich erreichbar ist.
Siehst du nach dem Umzug auf Nginx noch andere seltsame Dinge, etwa PHP-Fehler, die vorher still blieben, oder Plugins, die sich anders verhalten, lohnt sich ein Blick in die debug.log. Ein Serverumzug ist oft der Moment, in dem sich Fehler bemerkbar machen, die lange leise vor sich hin liefen.
Und wenn du sagst „Patrick, mit Serverumzügen und API-Debugging will ich mich einfach nicht herumschlagen“, ist das in Ordnung. Ich patche, du chillst. Dafür bin ich da.
Häufige Fragen zum Authorization-Header unter Nginx
Warum passiert das nur auf Nginx und nicht auf Apache?
Apache hat Redirects, besonders interne mod_rewrite-Regeln, oft ohne echten Roundtrip zum Client aufgelöst, dadurch blieb der Authorization-Header erhalten. Nginx schickt dem Client einen richtigen 301 und der Client lässt die Zugangsdaten beim Folgen weg, so wie es die RFC vorsieht. Nginx macht hier nichts falsch, Apache hat das Problem nur verdeckt.
Kann ich den Client anweisen, den Header trotzdem mitzuschicken?
Bei curl geht das mit --location-trusted statt -L. Damit sendet curl den Authorization-Header auch nach einem Redirect. Für Produktivcode ist das keine gute Idee, weil Zugangsdaten dann unkontrolliert an Redirect-Ziele gehen, auch wenn das Ziel irgendwann wechselt. Besser ist die richtige URL.
Meine Basis-URL zeigt schon auf www, trotzdem kommt ein 401.
Dann ist der Redirect nicht die Ursache. Prüfe, ob fastcgi_param HTTP_AUTHORIZATION $http_authorization; in deiner Nginx- oder FastCGI-Konfiguration steht und ob Application Passwords in deiner Installation aktiviert sind. Und kontrolliere mit curl -v direkt auf die www-Adresse, ob der Header tatsächlich ankommt.
Gilt das nur für Application Passwords?
Es gilt für alle Verfahren, die über den Authorization-Header laufen: Application Passwords, OAuth-Token, eigene Header. Cookie-basierte Anmeldung verhält sich anders, betrifft die REST API aber in der Regel nicht.
Wie finde ich heraus, auf welche Domain meine Installation eingestellt ist?
Unter Einstellungen → Allgemein stehen „WordPress-Adresse“ und „Website-Adresse“. Die URL dort, mit oder ohne www, ist die, auf die Nginx weiterleitet, und sie sollte auch deine API-Basis-URL sein.
Betrifft das nur die WordPress REST API?
Nein. Dasselbe passiert bei jedem API-Aufruf, der über einen Redirect läuft, ob REST, GraphQL oder eigene Endpunkte. Auch normale Formular-Posts können betroffen sein. Das ist Verhalten des HTTP-Clients, keine WordPress-Eigenheit.
Weitere Anleitungen
Was ein guter Wartungsbericht enthält
Drei Zahlen, ein paar Sätze und ein Satz, den du wirklich lesen willst.
Anleitung lesen Für InhaberGehackt, obwohl du nichts gemacht hast? Genau deshalb.
Die meisten Angriffe treffen Seiten, an denen seit Monaten niemand war.
Anleitung lesen GrundlagenDas Fehlerprotokoll von WordPress einschalten und verstehen
Deine Seite sagt dir, was kaputt ist. Man muss nur wissen, wo.
Anleitung lesen