Eine Weiterleitung wirkt simpel: Der Server antwortet mit einem Statuscode und einem Location-Header, und der Client ruft die genannte Adresse auf. Doch die drei Ziffern entscheiden darüber, ob Browser die Weiterleitung zwischenspeichern dürfen, ob ein POST ein POST bleibt und welche URL Suchmaschinen voraussichtlich indexieren.
Dieser Leitfaden behandelt die 3xx-Klasse nach RFC 9110, die Geschichte hinter ihren Überschneidungen, Weiterleitungen ohne Statuscode und die Konfiguration des richtigen Codes auf gängigen Servern.
Was die 3xx-Klasse bedeutet
Laut RFC 9110, der aktuellen Spezifikation der HTTP-Semantik, zeigt ein 3xx-Statuscode an, dass der User-Agent weitere Schritte unternehmen muss, um die Anfrage zu erfüllen („further action needs to be taken by the user agent in order to fulfill the request“). Abschnitt 15.4 unterteilt die Codes in vier Arten von Weiterleitung:
- Die Ressource ist unter einer anderen URI verfügbar, die in
Locationangegeben ist: 301, 302, 307 und 308. - Der Client kann zwischen mehreren Repräsentationen der Ressource wählen: 300.
- Eine andere Ressource liefert eine indirekte Antwort auf die Anfrage: 303.
- Der Client kann eine Kopie wiederverwenden, die bereits in seinem Cache liegt: 304.
In der Praxis funktionieren nur 301, 302, 303, 307 und 308 als automatische Weiterleitungen. Der Fetch Standard, den Browser umsetzen, bezeichnet genau diese fünf Codes als „redirect statuses“. Eine Antwort mit Code 300 wird meist wie eine normale Seite angezeigt, und Code 304 weist den Client an, die Kopie aus seinem Cache zu verwenden.
Der Wert von Location kann eine relative Referenz wie /new-page oder ../shop/ sein. Der Client löst sie gegen die URL auf, die er gerade angefragt hat (RFC 9110, Abschnitt 10.2.2). Enthält Location kein Fragment, bleibt das ursprüngliche erhalten: Ein Link auf /guide#install, der auf /docs/guide weiterleitet, endet bei /docs/guide#install.
Bei den fünf echten Weiterleitungen entscheiden zwei Fragen, welcher Code passt:
- Ist der Umzug dauerhaft oder vorübergehend? Permanente Weiterleitungen dürfen zwischengespeichert werden und signalisieren Suchmaschinen, stattdessen die neue URL zu indexieren.
- Muss die Anfragemethode erhalten bleiben? Das spielt eine Rolle, wenn die ursprüngliche Anfrage ein POST, PUT oder DELETE ist und kein einfaches GET.
Statuscodes für Weiterleitungen im Überblick
| Code | Name | Permanent | Methode bleibt erhalten | Standardmäßig cachebar | Typische Verwendung | Google-Suche |
|---|---|---|---|---|---|---|
| 300 | Multiple Choices | – | – | Ja | Alternative Repräsentationen anbieten (selten) | – |
| 301 | Moved Permanently | Ja | Nein: POST kann zu GET werden | Ja | Umgezogene Seiten und Websites, Kanonisierung von HTTPS und Host | Permanent (starkes Signal) |
| 302 | Found | Nein | Nein: POST kann zu GET werden | Nein | Kurzfristige Weiterleitungen, Login-Pflicht, A/B-Tests | Temporär (schwaches Signal) |
| 303 | See Other | Nein | Nein: wird zu GET | Nein | Ergebnisseite nach dem Absenden eines Formulars | Temporär |
| 304 | Not Modified | Keine Weiterleitung | – | – | Antwort auf eine bedingte Anfrage | Inhalt unverändert |
| 305 | Use Proxy | Veraltet | – | – | Nicht verwenden | – |
| 306 | (nicht verwendet) | Reserviert | – | – | Nicht verwenden | – |
| 307 | Temporary Redirect | Nein | Ja | Nein | Temporäre Weiterleitungen mit beliebiger Methode | Temporär |
| 308 | Permanent Redirect | Ja | Ja | Ja | Dauerhafte Umzüge von APIs, Formularen oder beliebigen URLs | Permanent (starkes Signal) |
„Standardmäßig cachebar“ bedeutet, dass der Code nach RFC 9110, Abschnitt 15.1 heuristisch cachebar ist: Ein Cache darf die Antwort wiederverwenden, ohne beim Server nachzufragen, auch wenn sie keine expliziten Angaben zur Gültigkeitsdauer enthält. Mit Cache-Control lässt sich trotzdem jede Antwort cachebar oder nicht cachebar machen. Die Google-Spalte fasst Googles Dokumentation zu Weiterleitungen und HTTP-Statuscodes zusammen.
Die Codes im Einzelnen
300 Multiple Choices
Die Ressource hat mehrere Repräsentationen, etwa verschiedene Sprachen oder Dateiformate, und der Client darf wählen. Der Body listet die Optionen meist auf, allerdings in keinem standardisierten Format. Eine bevorzugte Variante kann in Location genannt werden, doch Sie können sich nicht darauf verlassen, dass Browser ihr folgen. In der Praxis wird 300 fast nie verwendet: Serverseitige Content-Negotiation oder eine gewöhnliche Seite mit Links funktioniert besser.
301 Moved Permanently
Der Zielressource wurde eine neue, dauerhafte URI zugewiesen, und alle künftigen Verweise auf sie sollten eine der mitgelieferten URIs verwenden („has been assigned a new permanent URI and any future references to this resource ought to use one of the enclosed URIs“). Das ist der Standardcode für umgezogene Seiten, geänderte Domains, die Umstellung von HTTP auf HTTPS und Weiterleitungen auf den kanonischen Host.
Zwei Details sind wichtig. Eine 301-Weiterleitung ist heuristisch cachebar, Browser können sie sich also merken und den Server nicht mehr fragen (siehe den Abschnitt zum Caching weiter unten). Außerdem darf ein Client aus historischen Gründen beim Folgen einer 301 aus einem POST ein GET machen, und Browser tun das auch. Für Seiten, die per GET geladen werden, ist das unerheblich; für einen Formular-Endpunkt oder eine API verwenden Sie 308.
302 Found
Die Zielressource befindet sich vorübergehend unter einer anderen URI („resides temporarily under a different URI“), daher sollten Clients für künftige Anfragen weiterhin die ursprüngliche URL verwenden. Eine 302-Weiterleitung ist standardmäßig nicht cachebar. Setzen Sie sie für kurzlebige Situationen ein: eine Aktionsseite, die eine Woche lang eine Kategorie ersetzt, eine Login-Pflicht, geografisches Routing oder einen A/B-Test.
Wie bei 301 ist auch bei 302 der Wechsel von POST zu GET erlaubt. Viele Frameworks senden 302, wenn kein Code angegeben ist – deshalb enden dauerhafte Umzüge so oft versehentlich als 302.
303 See Other
Eine 303-Weiterleitung schickt den Client zu einer anderen Ressource, die als indirekte Antwort auf die ursprüngliche Anfrage dient, und der Client ruft sie per GET (oder HEAD) ab. Sie ist für das Post/Redirect/Get-Muster gedacht: Nach der Verarbeitung eines abgesendeten Formulars antwortet der Server mit 303 und der URL einer Bestätigungs- oder Ergebnisseite. Ein Neuladen dieser Seite wiederholt dann nur ein harmloses GET, statt das Formular erneut abzusenden.
Code 303 ist standardmäßig nicht cachebar und besagt nicht, dass die ursprüngliche URL umgezogen ist. Google behandelt ihn wie 302.
304 Not Modified (keine Weiterleitung)
Trotz seiner Nummer im 3xx-Bereich schickt Code 304 den Client nirgendwohin. Er beantwortet eine bedingte GET- oder HEAD-Anfrage, also eine, die If-None-Match mit einem ETag-Wert oder If-Modified-Since mit einem Datum enthält. Hat sich die Ressource nicht geändert, antwortet der Server mit 304 ohne Body, und der Client verwendet die Kopie aus seinem Cache.
GET /styles.css HTTP/1.1
Host: example.com
If-None-Match: "5f1c-3a2b"
HTTP/1.1 304 Not Modified
ETag: "5f1c-3a2b"
Cache-Control: max-age=3600Redirect-Checker senden keine bedingten Anfragen und bekommen deshalb normalerweise nie einen 304 zu sehen. Um ihn in TrueURL zu testen, führen Sie eine normale Prüfung durch, kopieren den ETag-Wert aus den Antwort-Headern und senden ihn unter Benutzerdefinierte Anfrage-Header als If-None-Match-Header. Für den Googlebot bedeutet ein 304, dass sich der Inhalt seit dem letzten Crawling nicht geändert hat.
305 Use Proxy und 306 (nicht verwendet)
Code 305 wies den Client an, die Anfrage über den in Location genannten Proxy zu wiederholen. Dass eine Antwort den Proxy eines Clients umkonfigurieren kann, ist ein Sicherheitsrisiko. Deshalb hat RFC 7231 den Code für veraltet erklärt, und aktuelle Browser reagieren nicht darauf.
306 war in einer früheren Version der Spezifikation definiert (oft als Switch Proxy aufgeführt) und ist heute reserviert. Keiner der beiden Codes hat in einer modernen Anwendung etwas zu suchen; taucht einer auf, ist etwas falsch konfiguriert.
307 Temporary Redirect
Code 307 bedeutet dasselbe wie 302, mit einem Zusatz: Der Client darf die Anfragemethode nicht ändern. Ein POST wird als POST mit demselben Body an die neue URL gesendet. Damit ist 307 die richtige temporäre Weiterleitung für APIs und Formular-Endpunkte.
In den Chrome-DevTools begegnet Ihnen 307 womöglich auch als 307 Internal Redirect von http:// zu https://. Diesen hat der Server nie gesendet. Der Browser hat die Anfrage selbst auf HTTPS umgestellt, meist wegen einer HSTS-Richtlinie, die er für die Website gespeichert hat.
308 Permanent Redirect
Code 308 ist das methodenerhaltende Gegenstück zu 301: permanent, heuristisch cachebar, und der Client darf die Methode nicht ändern. Er wurde 2014 erstmals im experimentellen RFC 7238 veröffentlicht, 2015 mit RFC 7538 zum Standard erhoben und ist heute Teil von RFC 9110.
Verwenden Sie 308 für den dauerhaften Umzug von Endpunkten, die POST-, PUT- oder DELETE-Anfragen empfangen. Bei gewöhnlichen Seiten sind 301 und 308 austauschbar, und Google behandelt beide als permanente Weiterleitungen.
Warum es 303, 307 und 308 gibt
Die Überschneidung zwischen 301/302 und 303/307/308 ist ein historischer Unfall.
HTTP/1.0 (RFC 1945, 1996) definierte 301 Moved Permanently und 302 Moved Temporarily. Demnach durfte ein User-Agent einen POST nicht ohne Bestätigung durch den Nutzer automatisch weiterleiten, und schon damals hielt die Spezifikation fest, dass manche User-Agents den POST fälschlicherweise in ein GET umwandelten. Die Browser blieben dabei.
HTTP/1.1 ergänzte zwei Codes, die keinen Interpretationsspielraum lassen. RFC 2068 (1997) führte 303 See Other ein, der immer zu GET wechselt. RFC 2616 (1999) fügte 307 Temporary Redirect hinzu, der die Methode immer beibehält, benannte 302 in Found um und räumte offen ein, dass die meisten User-Agents eine 302 wie eine 303 behandelten.
Damit fehlte aber noch immer eine permanente Weiterleitung, die die Methode beibehält. Diese Lücke schloss 308 erst fünfzehn Jahre später. Unterdessen fügte sich die Spezifikation der Realität: Seit RFC 7231 (2014) darf ein User-Agent nach einer 301 oder 302 aus einem POST ein GET machen.
Heute wenden Browser diese Regeln aus dem Fetch Standard an:
| Status | Ursprüngliche Methode | Methode der Anfrage an die neue URL |
|---|---|---|
| 301, 302 | POST | GET, ohne Body |
| 301, 302 | Jede andere Methode | Dieselbe Methode |
| 303 | Jede Methode außer GET und HEAD | GET, ohne Body |
| 303 | GET oder HEAD | Dieselbe Methode |
| 307, 308 | Jede Methode | Dieselbe Methode und derselbe Body |
Nach einer 301 oder 302 wird nur POST umgeschrieben; PUT und DELETE behalten ihre Methode. Darauf sollten Sie sich jedoch nicht verlassen, denn HTTP-Bibliotheken verhalten sich unterschiedlich. Verwenden Sie daher 307 oder 308, wann immer es auf die Methode ankommt.
Weiterleitungen ohne 3xx-Statuscode
Meta-Refresh
<meta http-equiv="refresh" content="0; url=https://example.com/new-page">Die Seite selbst wird mit 200 OK ausgeliefert, und der Browser folgt dem Tag, nachdem er sie geladen hat. Die Zahl ist die Verzögerung in Sekunden. Google interpretiert einen sofortigen Meta-Refresh als permanente und einen verzögerten als temporäre Weiterleitung.
Zeitgesteuerte Refreshes schaden außerdem der Barrierefreiheit. Die WCAG akzeptieren einen sofortigen Meta-Refresh als clientseitige Weiterleitung (Technik H76), werten einen zeitgesteuerten jedoch als Fehler (F40), weil sich die Seite nach einem Timer ändert, den Nutzer nicht steuern können.
JavaScript-Weiterleitungen
<script>
window.location.replace('https://example.com/new-page');
</script>location.replace() hinterlässt die weiterleitende Seite nicht im Sitzungsverlauf, sodass der Zurück-Button Nutzer nicht erneut in die Weiterleitung schickt. Eine Zuweisung an location.href hinterlässt sie dagegen dort.
Google führt JavaScript erst beim Rendern einer Seite aus, also nach dem Crawling, und das Rendern kann fehlschlagen. Die Google-Dokumentation empfiehlt JavaScript-Weiterleitungen nur dann, wenn serverseitige und Meta-Refresh-Weiterleitungen nicht möglich sind. Viele andere Bots und Tools führen überhaupt kein JavaScript aus.
Der Refresh-Header
HTTP/1.1 200 OK
Content-Type: text/html
Refresh: 0; url=https://example.com/new-pageDer Refresh-Header ist in keinem HTTP-RFC definiert. Browser unterstützen ihn seit Jahrzehnten, und der HTML Standard behandelt ihn wie einen Meta-Refresh-Tag. Viele HTTP-Clients und Tools, darunter curl, ignorieren ihn. Verwenden Sie daher lieber eine echte 3xx-Weiterleitung.
Weiterleitungen richtig einrichten
nginx
# Jede http://-Anfrage, egal an welchen Host, geht mit einem Hop zur kanonischen HTTPS-URL
server {
listen 80;
server_name example.com www.example.com;
return 301 https://example.com$request_uri;
}
# https://www.example.com -> https://example.com
server {
listen 443 ssl;
server_name www.example.com;
ssl_certificate /etc/ssl/example.com.crt;
ssl_certificate_key /etc/ssl/example.com.key;
return 301 https://example.com$request_uri;
}
server {
listen 443 ssl;
server_name example.com;
# ssl_certificate und ssl_certificate_key wie oben
# Eine umgezogene Seite
location = /old-page {
return 301 /new-page;
}
# Ein ganzer Bereich, der Rest des Pfads bleibt erhalten
location /blog/ {
rewrite ^/blog/(.*)$ /articles/$1 permanent;
}
}$request_uri enthält den ursprünglichen Pfad samt Query-String, sodass nichts verloren geht. rewrite ... permanent sendet eine 301, rewrite ... redirect eine 302; für 307 oder 308 verwenden Sie return mit dem jeweiligen Code.
Apache (.htaccess)
# mod_alias: eine Seite und ein ganzer Ordner (der Rest des Pfads wird angehängt)
Redirect 301 /old-page https://example.com/new-page
Redirect 301 /blog/ https://example.com/articles/
# mod_alias mit regulärem Ausdruck
RedirectMatch 301 ^/products/([0-9]+)\.html$ https://example.com/p/$1
# mod_rewrite: jede http://- und www-Anfrage mit einem Hop an https://example.com senden
RewriteEngine On
RewriteCond %{HTTPS} off [OR]
RewriteCond %{HTTP_HOST} ^www\. [NC]
RewriteRule ^ https://example.com%{REQUEST_URI} [R=301,L]Redirect ohne Statuscode sendet eine 302, geben Sie den Code also immer an. Hinter einem Load Balancer oder CDN, das sich per unverschlüsseltem HTTP mit Apache verbindet, steht %{HTTPS} immer auf off, und die letzte Regel erzeugt eine Schleife; prüfen Sie stattdessen %{HTTP:X-Forwarded-Proto}. mod_alias und mod_rewrite arbeiten unabhängig voneinander, und die Reihenfolge der Zeilen in der Datei bestimmt nicht, welches Modul zuerst greift. Eine URL, auf die Regeln aus beiden Modulen passen, wie hier http://www.example.com/old-page, kann zwei Hops benötigen. Bündeln Sie bei URLs, die mit einem einzigen Hop weiterleiten müssen, alle zugehörigen Regeln in einem Modul.
Cloudflare
Cloudflare kann bereits am Edge weiterleiten, bevor eine Anfrage Ihren Server erreicht:
Single Redirects (im Dashboard im Bereich Redirect Rules) erkennen Anfragen anhand eines Filterausdrucks und leiten sie mit 301, 302, 303, 307 oder 308 auf ein statisches oder dynamisches Ziel weiter. Eine typische Regel von www auf die Apex-Domain hat diese Einstellungen:
When incoming requests match: (http.host eq "www.example.com") URL redirect type: Dynamic Expression: concat("https://example.com", http.request.uri.path) Status code: 301 Preserve query string: OnBulk Redirects verarbeiten lange Listen von Quell- und Ziel-URLs, etwa eine als CSV hochgeladene Migrationstabelle. Jeder Eintrag hat einen eigenen Statuscode und Optionen wie die Übernahme des Query-Strings; aktiviert wird die Liste über eine Bulk Redirect Rule.
Workers können Weiterleitungen im Code mit
Response.redirect()erzeugen:
export default {
async fetch(request) {
const url = new URL(request.url);
if (url.pathname === '/old-page') {
url.pathname = '/new-page'; // der Query-String bleibt erhalten
return Response.redirect(url.toString(), 301);
}
return fetch(request);
},
};Response.redirect() verwendet standardmäßig 302 und akzeptiert nur 301, 302, 303, 307 und 308. Jeder andere Code löst einen RangeError aus.
Node.js und Express
import express from 'express';
const app = express();
app.set('trust proxy', 1); // bei Betrieb hinter einem Load Balancer oder CDN
// HTTPS und kanonischer Host mit einem Hop
app.use((req, res, next) => {
if (req.secure && req.hostname === 'example.com') return next();
res.redirect(301, `https://example.com${req.originalUrl}`);
});
// Eine umgezogene Seite
app.get('/old-page', (req, res) => {
res.redirect(301, '/new-page');
});res.redirect(url) ohne Statuscode sendet eine 302. Express 5 unterstützt nur die Reihenfolge res.redirect(status, url). In reinem Node.js setzen Sie Status und Header selbst:
res.writeHead(301, { Location: 'https://example.com/new-page' });
res.end();PHP
<?php
// Muss vor jeder Ausgabe ausgeführt werden.
header('Location: https://example.com/new-page', true, 301);
exit;Ein Location-Header allein lässt PHP eine 302 senden (sofern nicht bereits ein Status 201 oder 3xx gesetzt wurde). Übergeben Sie den Code daher als drittes Argument, und beenden Sie das Skript mit exit, damit danach nichts mehr ausgeführt wird. In WordPress verwenden auch wp_redirect() und wp_safe_redirect() standardmäßig 302; übergeben Sie 301 als zweites Argument.
Den richtigen Weiterleitungscode wählen
- Die Seite ist dauerhaft umgezogen, und es handelt sich um eine gewöhnliche, per GET geladene Seite: Verwenden Sie 301. Eine 308 funktioniert genauso gut.
- Der Umzug ist dauerhaft, und Clients senden möglicherweise POST, PUT oder DELETE, etwa bei APIs, Formular-Actions und Webhooks: Verwenden Sie 308.
- Die Weiterleitung ist temporär, und es kommt nur auf GET an: Verwenden Sie 302.
- Die Weiterleitung ist temporär, und die Methode muss erhalten bleiben: Verwenden Sie 307.
- Ein Formular oder ein anderer POST wurde verarbeitet, und der Nutzer soll eine Ergebnisseite sehen: Verwenden Sie 303.
- HTTP zu HTTPS: Verwenden Sie 301 oder 308, und senden Sie von der HTTPS-Website einen
Strict-Transport-Security-Header, damit Browser die unsichere Anfrage beim nächsten Mal überspringen. - Der Inhalt existiert nicht mehr und hat keine Entsprechung: Leiten Sie nicht weiter. Liefern Sie 404 oder 410. Google kann eine Weiterleitung auf eine thematisch fremde Seite, etwa die Startseite, als Soft 404 behandeln.
- 300, 305 und 306: Verwenden Sie diese Codes nicht. Code 304 erzeugt Ihr Server oder Cache bei bedingten Anfragen; er ist nie eine Option für eine Weiterleitung.
Die Caching-Falle bei 301 und 308
Da 301 und 308 heuristisch cachebar sind, können Browser sie speichern und den Server nicht mehr fragen. Chromium-basierte Browser behalten eine 301 oder 308 ohne Caching-Header so lange, bis der Eintrag verdrängt wird oder der Nutzer den Cache leert. Eine falsche permanente Weiterleitung kann frühere Besucher daher noch lange, nachdem Sie den Server korrigiert haben, an die falsche Stelle schicken, und Sie können sie nicht zuverlässig aus deren Browsern zurückholen.
So gehen Sie auf Nummer sicher:
- Testen Sie neue Regeln zunächst mit 302 oder 307, und wechseln Sie zu 301 oder 308, sobald das Ziel bestätigt ist.
- Alternativ begrenzen Sie während der Einführung, wie lange die permanente Weiterleitung zwischengespeichert werden darf:
location = /old-page {
add_header Cache-Control "max-age=3600";
return 301 https://example.com/new-page;
}- Verwenden Sie beim Testen ein privates Fenster, die DevTools mit Disable cache oder einen serverseitigen Checker, der nie einen Browser-Cache nutzt.
- Leeren Sie nach jeder Änderung an einer Weiterleitung die CDN- und Proxy-Caches, denn auch dort können Weiterleitungen gespeichert sein.
- Versuchen Sie nicht, eine zwischengespeicherte Weiterleitung A → B rückgängig zu machen, indem Sie B zurück auf A weiterleiten. Besucher, deren Browser die erste Weiterleitung gespeichert hat, können sonst in einer Schleife landen.
Weiterleitungen testen
Ganz gleich, welche Plattform Sie verwenden: Prüfen Sie das Ergebnis von außen. Der 301-Redirect-Checker bestätigt, dass jeder Hop eines dauerhaften Umzugs eine 301 oder 308 ist, der Weiterleitungsketten-Checker zeigt jeden Hop mit seinem Location-Wert und den Headern, und der HTTPS-Redirect-Checker testet alle vier Protokoll- und Host-Varianten auf einmal. Der Meta-Refresh-Checker findet clientseitige Weiterleitungen, die reinen Header-Tools entgehen, und der Weiterleitungs-Latenztest misst, was jeder Hop kostet.
Im Terminal gibt curl -sIL https://example.com/old-page die Header jedes Hops aus. Unser Leitfaden URL-Weiterleitungen prüfen behandelt die Kommandozeile, die DevTools und häufige Probleme mit Weiterleitungen ausführlich.