Une redirection paraît simple : le serveur répond avec un code de statut et un en-tête Location, et le client s’y rend. Mais ces trois chiffres déterminent si les navigateurs peuvent mettre la redirection en cache, si un POST reste un POST et quelle URL les moteurs de recherche indexeront probablement.

Ce guide couvre la classe 3xx telle que la définit la RFC 9110, l’histoire de ses chevauchements, les redirections sans code de statut et la façon de configurer le bon code sur les serveurs courants.

Ce que signifie la classe 3xx

La RFC 9110, la spécification actuelle de la sémantique HTTP, indique qu’un code de statut 3xx signifie qu’« une action supplémentaire doit être effectuée par l’agent utilisateur pour satisfaire la requête ». Sa section 15.4 répartit les codes en quatre types de redirection :

  • La ressource est disponible à un autre URI, indiqué dans Location : 301, 302, 307 et 308.
  • Le client peut choisir parmi plusieurs représentations de la ressource : 300.
  • Une autre ressource fournit une réponse indirecte à la requête : 303.
  • Le client peut réutiliser une copie qu’il possède déjà en cache : 304.

En pratique, seuls les codes 301, 302, 303, 307 et 308 fonctionnent comme des redirections automatiques. Le standard Fetch, que les navigateurs implémentent, qualifie précisément ces cinq codes de « statuts de redirection » (redirect statuses). Un code 300 s’affiche généralement comme une page normale, et un code 304 indique au client d’utiliser la copie qu’il a en cache.

La valeur de Location peut être une référence relative, comme /new-page ou ../shop/. Le client la résout par rapport à l’URL qu’il vient de demander (RFC 9110, section 10.2.2). Si Location ne contient pas de fragment, celui d’origine est conservé : un lien vers /guide#install qui redirige vers /docs/guide aboutit à /docs/guide#install.

Pour les cinq vraies redirections, deux questions déterminent le code adapté :

  1. Le déplacement est-il permanent ou temporaire ? Les redirections permanentes peuvent être mises en cache, et elles indiquent aux moteurs de recherche d’indexer plutôt la nouvelle URL.
  2. La méthode de requête doit-elle être conservée ? La question se pose lorsque la requête d’origine est un POST, un PUT ou un DELETE plutôt qu’un simple GET.

Les codes de redirection en un coup d’œil

Code Nom Permanente Méthode conservée Mise en cache possible par défaut Usage type Recherche Google
300 Multiple Choices – – Oui Proposer plusieurs représentations (rare) –
301 Moved Permanently Oui Non : un POST peut devenir GET Oui Pages et sites déplacés, passage en HTTPS et canonicalisation de l’hôte Permanente (signal fort)
302 Found Non Non : un POST peut devenir GET Non Redirections de courte durée, connexion obligatoire, tests A/B Temporaire (signal faible)
303 See Other Non Non : devient GET Non Page de résultat après l’envoi d’un formulaire Temporaire
304 Not Modified Pas une redirection – – Réponse à une requête conditionnelle Contenu inchangé
305 Use Proxy Obsolète – – À ne pas utiliser –
306 (inutilisé) Réservé – – À ne pas utiliser –
307 Temporary Redirect Non Oui Non Redirections temporaires, quelle que soit la méthode Temporaire
308 Permanent Redirect Oui Oui Oui Déplacements permanents d’API, de formulaires ou de toute autre URL Permanente (signal fort)

« Mise en cache possible par défaut » signifie que le code peut être mis en cache de manière heuristique au sens de la RFC 9110, section 15.1 : un cache peut réutiliser la réponse sans consulter le serveur, même si elle ne contient aucune information explicite de fraîcheur. Avec Cache-Control, on peut toutefois autoriser ou interdire la mise en cache de n’importe quelle réponse. La colonne Google résume la documentation de Google sur les redirections et les codes de statut HTTP.

Les codes un par un

300 Multiple Choices

La ressource possède plusieurs représentations, par exemple en différentes langues ou dans différents formats de fichier, et le client peut choisir. Le corps de la réponse liste généralement les options, sans format standard. Un choix préféré peut être indiqué dans Location, mais rien ne garantit que les navigateurs le suivront. En pratique, le code 300 n’est presque jamais utilisé : une négociation de contenu côté serveur ou une page ordinaire avec des liens fonctionne mieux.

301 Moved Permanently

La ressource cible « s’est vu attribuer un nouvel URI permanent, et toute référence future à cette ressource devrait utiliser l’un des URI fournis ». C’est le code standard pour les pages déplacées, les changements de domaine, le passage de HTTP à HTTPS et les redirections vers l’hôte canonique.

Deux détails comptent. Une redirection 301 peut être mise en cache de manière heuristique : les navigateurs peuvent donc la mémoriser et ne plus interroger le serveur (voir la section consacrée au cache plus bas). Par ailleurs, pour des raisons historiques, un client peut transformer un POST en GET lorsqu’il suit une 301, et c’est ce que font les navigateurs. Pour les pages chargées en GET, cela n’a aucune importance ; pour l’URL qui traite un formulaire ou pour une API, utilisez 308.

302 Found

La ressource cible « se trouve temporairement sous un URI différent » : les clients doivent donc continuer d’utiliser l’URL d’origine pour leurs futures requêtes. Une redirection 302 n’est pas mise en cache par défaut. Utilisez-la pour des situations de courte durée : une page promotionnelle qui remplace une catégorie pendant une semaine, une connexion obligatoire, un routage géographique ou un test A/B.

Comme la 301, la 302 autorise le passage de POST à GET. De nombreux frameworks envoient une 302 lorsqu’aucun code n’est précisé : voilà pourquoi tant de déplacements permanents finissent par erreur en 302.

303 See Other

Une redirection 303 envoie le client vers une autre ressource qui sert de réponse indirecte à la requête d’origine, et le client la récupère en GET (ou en HEAD). Elle est conçue pour le modèle Post/Redirect/Get : après avoir traité l’envoi d’un formulaire, le serveur répond 303 avec l’URL d’une page de confirmation ou de résultat. Si l’utilisateur recharge ensuite cette page, le navigateur répète un GET sans conséquence au lieu de renvoyer le formulaire.

Une redirection 303 n’est pas mise en cache par défaut, et elle n’affirme pas que l’URL d’origine a été déplacée. Google la traite comme une 302.

304 Not Modified (pas une redirection)

Malgré son numéro en 3xx, un code 304 n’envoie jamais le client ailleurs. Il répond à une requête GET ou HEAD conditionnelle, c’est-à-dire qui contient If-None-Match avec une valeur ETag, ou If-Modified-Since avec une date. Si la ressource n’a pas changé, le serveur répond 304 sans corps, et le client utilise la copie qu’il a en cache.

HTTP
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=3600

Les vérificateurs de redirections envoient des requêtes non conditionnelles : ils ne rencontrent donc normalement jamais de 304. Pour tester ce cas dans TrueURL, lancez une vérification normale, copiez la valeur ETag dans ses en-têtes de réponse, puis envoyez-la dans un en-tête If-None-Match via les En-têtes de requête personnalisés. Pour Googlebot, un code 304 signifie que le contenu n’a pas changé depuis la dernière exploration.

305 Use Proxy et 306 (inutilisé)

Un code 305 demandait au client de répéter la requête via le proxy indiqué dans Location. Permettre à une réponse de reconfigurer le proxy d’un client pose un risque de sécurité : la RFC 7231 a donc rendu ce code obsolète, et les navigateurs actuels n’en tiennent pas compte.

Le code 306 était défini dans une version antérieure de la spécification (on le trouve souvent sous le nom Switch Proxy) et il est désormais réservé. Aucun de ces deux codes n’a sa place dans une application moderne ; si vous en voyez un, quelque chose est mal configuré.

307 Temporary Redirect

Une redirection 307 a le même sens qu’une 302, avec une règle en plus : le client ne doit pas changer la méthode de la requête. Un POST est renvoyé en POST, avec le même corps, vers la nouvelle URL. La 307 est donc la redirection temporaire adaptée aux API et aux URL qui traitent des formulaires.

Vous pouvez aussi croiser une 307 dans les outils de développement de Chrome (DevTools), sous la forme d’une 307 Internal Redirect de http:// vers https://. Le serveur ne l’a jamais envoyée. Le navigateur a converti lui-même la requête en HTTPS, généralement en raison d’une politique HSTS qu’il a enregistrée pour le site.

308 Permanent Redirect

La redirection 308 est le pendant de la 301 qui préserve la méthode : elle est permanente, peut être mise en cache de manière heuristique, et le client ne doit pas modifier la méthode. Elle a d’abord été publiée en 2014 dans la RFC 7238, expérimentale, est devenue un standard avec la RFC 7538 en 2015 et fait aujourd’hui partie de la RFC 9110.

Utilisez la 308 pour les déplacements permanents d’URL qui reçoivent des requêtes POST, PUT ou DELETE. Pour les pages ordinaires, 301 et 308 sont interchangeables, et Google traite les deux comme des redirections permanentes.

Pourquoi les codes 303, 307 et 308 existent

Le chevauchement entre 301/302 et 303/307/308 est un accident de l’histoire.

HTTP/1.0 (RFC 1945, 1996) définissait 301 Moved Permanently et 302 Moved Temporarily. Il précisait qu’un client ne doit pas rediriger automatiquement un POST sans la confirmation de l’utilisateur, et notait déjà que certains clients transformaient à tort le POST en GET. Les navigateurs ont continué à le faire.

HTTP/1.1 a ajouté deux codes qui ne laissent aucune place à l’interprétation. La RFC 2068 (1997) a introduit 303 See Other, qui passe toujours en GET. La RFC 2616 (1999) a ajouté 307 Temporary Redirect, qui conserve toujours la méthode, a renommé 302 en Found et a reconnu ouvertement que la plupart des clients traitaient une 302 comme une 303.

Il manquait encore une redirection permanente qui conserve la méthode. La 308 a comblé ce vide quinze ans plus tard. Entre-temps, la spécification a pris acte de la réalité : depuis la RFC 7231 (2014), un client peut transformer un POST en GET après une 301 ou une 302.

Aujourd’hui, les navigateurs appliquent ces règles du standard Fetch :

Statut Méthode d’origine Méthode utilisée par les navigateurs pour la nouvelle URL
301, 302 POST GET, sans le corps
301, 302 Toute autre méthode La même méthode
303 Toute méthode sauf GET et HEAD GET, sans le corps
303 GET ou HEAD La même méthode
307, 308 Toute méthode La même méthode et le même corps

Seul le POST est réécrit après une 301 ou une 302 ; un PUT ou un DELETE conserve sa méthode. Il est risqué de s’y fier, car les bibliothèques HTTP ne se comportent pas toutes de la même façon : utilisez 307 ou 308 dès que la méthode compte.

Redirections sans code de statut 3xx

Meta refresh

HTML
<meta http-equiv="refresh" content="0; url=https://example.com/new-page">

La page elle-même est livrée avec un 200 OK, et le navigateur suit la balise après l’avoir chargée. Le nombre correspond au délai en secondes. Google interprète une balise meta refresh instantanée comme une redirection permanente, et une balise différée comme une redirection temporaire.

Les actualisations temporisées nuisent aussi à l’accessibilité. Les WCAG acceptent une balise meta refresh instantanée comme redirection côté client (technique H76), mais considèrent une actualisation temporisée comme un échec (F40), car la page change selon une minuterie que l’utilisateur ne peut pas contrôler.

Redirections JavaScript

HTML
<script>
  window.location.replace('https://example.com/new-page');
</script>

location.replace() ne laisse pas la page de redirection dans l’historique de session : le bouton Retour ne renvoie donc pas l’utilisateur dans la redirection. Affecter une valeur à location.href, en revanche, l’y laisse.

Google n’exécute le JavaScript que lorsqu’il effectue le rendu d’une page, après l’avoir explorée, et ce rendu peut échouer. Sa documentation ne recommande les redirections JavaScript que lorsque les redirections côté serveur et meta refresh sont impossibles. De nombreux autres robots et outils n’exécutent jamais le JavaScript.

L’en-tête Refresh

HTTP
HTTP/1.1 200 OK
Content-Type: text/html
Refresh: 0; url=https://example.com/new-page

L’en-tête Refresh n’est défini dans aucune RFC HTTP. Les navigateurs le prennent en charge depuis des décennies, et le standard HTML le traite comme l’équivalent d’une balise meta refresh. De nombreux clients et outils HTTP, dont curl, l’ignorent : préférez une vraie redirection 3xx.

Mettre en place correctement les redirections

nginx

nginx
# Toute requête http://, quel que soit l’hôte, va vers l’URL HTTPS canonique en un seul saut
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 et ssl_certificate_key comme ci-dessus

    # Une page déplacée
    location = /old-page {
        return 301 /new-page;
    }

    # Toute une section, en conservant le reste du chemin
    location /blog/ {
        rewrite ^/blog/(.*)$ /articles/$1 permanent;
    }
}

$request_uri contient le chemin et la chaîne de requête d’origine : rien n’est perdu. rewrite ... permanent envoie une 301 et rewrite ... redirect une 302 ; pour une 307 ou une 308, utilisez return avec le code voulu.

Apache (.htaccess)

Apache
# mod_alias : une page, puis tout un dossier (le reste du chemin est ajouté)
Redirect 301 /old-page https://example.com/new-page
Redirect 301 /blog/ https://example.com/articles/

# mod_alias avec une expression régulière
RedirectMatch 301 ^/products/([0-9]+)\.html$ https://example.com/p/$1

# mod_rewrite : toute requête http:// ou www vers https://example.com en un seul saut
RewriteEngine On
RewriteCond %{HTTPS} off [OR]
RewriteCond %{HTTP_HOST} ^www\. [NC]
RewriteRule ^ https://example.com%{REQUEST_URI} [R=301,L]

Sans code de statut, Redirect envoie une 302 : indiquez donc toujours le code. Derrière un répartiteur de charge ou un CDN qui se connecte à Apache en HTTP non chiffré, %{HTTPS} vaut toujours off et la dernière règle tourne en boucle ; testez plutôt %{HTTP:X-Forwarded-Proto}. mod_alias et mod_rewrite fonctionnent indépendamment l’un de l’autre, et l’ordre des lignes dans le fichier ne détermine pas quel module s’exécute en premier. Une URL qui correspond à des règles des deux modules, comme http://www.example.com/old-page ici, peut nécessiter deux sauts (deux étapes de redirection successives) ; pour les URL qui doivent rediriger en un seul saut, regroupez toutes leurs règles dans un même module.

Cloudflare

Cloudflare peut rediriger directement sur son réseau edge, avant même qu’une requête n’atteigne votre serveur :

  • Les Single Redirects (section Redirect Rules du tableau de bord) sélectionnent les requêtes à l’aide d’une expression de filtre et les envoient vers une cible statique ou dynamique avec un code 301, 302, 303, 307 ou 308. Une règle typique de www vers le domaine racine utilise ces paramètres :

    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:         On
  • Les Bulk Redirects gèrent de longues listes d’URL source et cible, comme un plan de migration importé au format CSV. Chaque entrée a son propre code de statut et ses propres options, comme la conservation de la chaîne de requête, et une Bulk Redirect Rule active la liste.

  • Les Workers peuvent générer des redirections dans le code avec Response.redirect() :

JavaScript
export default {
  async fetch(request) {
    const url = new URL(request.url);
    if (url.pathname === '/old-page') {
      url.pathname = '/new-page'; // la chaîne de requête est conservée
      return Response.redirect(url.toString(), 301);
    }
    return fetch(request);
  },
};

Response.redirect() utilise 302 par défaut et n’accepte que 301, 302, 303, 307 et 308. Tout autre code lève une RangeError.

Node.js et Express

JavaScript
import express from 'express';

const app = express();
app.set('trust proxy', 1); // derrière un répartiteur de charge ou un CDN

// HTTPS et hôte canonique en un seul saut
app.use((req, res, next) => {
  if (req.secure && req.hostname === 'example.com') return next();
  res.redirect(301, `https://example.com${req.originalUrl}`);
});

// Une page déplacée
app.get('/old-page', (req, res) => {
  res.redirect(301, '/new-page');
});

res.redirect(url) sans statut envoie une 302. Express 5 n’accepte que l’ordre res.redirect(status, url). En Node.js pur, définissez vous-même le statut et l’en-tête :

JavaScript
res.writeHead(301, { Location: 'https://example.com/new-page' });
res.end();

PHP

PHP
<?php
// À exécuter avant tout envoi de contenu.
header('Location: https://example.com/new-page', true, 301);
exit;

Avec un simple en-tête Location, PHP envoie une 302 (sauf si un statut 201 ou 3xx a déjà été défini) : passez donc le code en troisième argument, et appelez exit pour que rien d’autre ne s’exécute. Dans WordPress, wp_redirect() et wp_safe_redirect() utilisent aussi 302 par défaut ; passez 301 en deuxième argument.

Choisir le bon code de redirection

  • La page a été déplacée définitivement et il s’agit d’une page ordinaire chargée en GET : utilisez 301. Une 308 fonctionne tout aussi bien.
  • Le déplacement est permanent et les clients peuvent envoyer des requêtes POST, PUT ou DELETE, comme avec les API, les actions de formulaire et les webhooks : utilisez 308.
  • La redirection est temporaire et seul le GET compte : utilisez 302.
  • La redirection est temporaire et la méthode doit être conservée : utilisez 307.
  • Un formulaire ou un autre POST a été traité et l’utilisateur doit voir une page de résultat : utilisez 303.
  • De HTTP vers HTTPS : utilisez 301 ou 308, et envoyez un en-tête Strict-Transport-Security depuis le site HTTPS pour que les navigateurs se passent de la requête non sécurisée la fois suivante.
  • Le contenu a disparu et n’a pas d’équivalent : ne redirigez pas. Renvoyez un code 404 ou 410. Google peut considérer une redirection vers une page sans rapport, comme la page d’accueil, comme une erreur soft 404.
  • 300, 305 et 306 : ne les utilisez pas. Un code 304 est produit par votre serveur ou votre cache en réponse aux requêtes conditionnelles ; ce n’est jamais un choix de redirection.

Le piège du cache avec 301 et 308

Comme les redirections 301 et 308 peuvent être mises en cache de manière heuristique, les navigateurs peuvent les conserver et ne plus interroger le serveur. Les navigateurs basés sur Chromium conservent une 301 ou une 308 sans en-têtes de cache jusqu’à ce que l’entrée soit évincée du cache ou que l’utilisateur le vide. Une redirection permanente erronée peut continuer d’envoyer vos anciens visiteurs au mauvais endroit longtemps après la correction du serveur, et vous n’avez aucun moyen fiable de la retirer de leurs navigateurs.

Pour éviter les mauvaises surprises :

  • Testez d’abord les nouvelles règles avec une 302 ou une 307, puis passez à 301 ou 308 une fois la destination confirmée.
  • Vous pouvez aussi limiter la durée pendant laquelle la redirection permanente peut être mise en cache, le temps de son déploiement :
nginx
location = /old-page {
    add_header Cache-Control "max-age=3600";
    return 301 https://example.com/new-page;
}
  • Pour vos tests, utilisez une fenêtre de navigation privée ou les DevTools avec Désactiver le cache (Disable cache), ou bien un vérificateur côté serveur, qui n’utilise jamais de cache de navigateur.
  • Videz les caches du CDN et des proxys après avoir modifié une redirection, car ils peuvent eux aussi stocker des redirections.
  • N’essayez pas d’annuler une redirection A → B mise en cache en redirigeant B vers A. Les visiteurs dont le navigateur a mis en cache la première redirection risquent de se retrouver dans une boucle.

Tester vos redirections

Quelle que soit votre plateforme, vérifiez le résultat depuis l’extérieur. Le Vérificateur de redirection 301 confirme que chaque saut d’un déplacement permanent est une 301 ou une 308, le Vérificateur de chaîne de redirections affiche chaque saut avec son Location et ses en-têtes, et le Vérificateur de redirection HTTPS teste en une fois les quatre variantes de protocole et d’hôte. Le Vérificateur de meta refresh détecte les redirections côté client qui échappent aux outils limités aux en-têtes, et le Testeur de latence des redirections mesure ce que coûte chaque saut.

Depuis un terminal, curl -sIL https://example.com/old-page affiche les en-têtes de chaque saut. Notre guide pour vérifier les redirections d’une URL détaille la ligne de commande, les DevTools et les problèmes de redirection courants.