Um redirecionamento parece simples: o servidor responde com um código de status e um cabeçalho Location, e o cliente vai até lá. Mas esses três dígitos decidem se os navegadores podem guardar o redirecionamento em cache, se um POST continua sendo POST e qual URL os mecanismos de busca tendem a indexar.

Este guia trata da classe 3xx conforme definida na RFC 9110, da história por trás das sobreposições entre os códigos, dos redirecionamentos sem código de status e de como configurar o código certo nos servidores mais comuns.

O que significa a classe 3xx

A RFC 9110, a especificação atual da semântica do HTTP, diz que um código de status 3xx indica que “o agente de usuário (user agent) precisa tomar alguma ação adicional para atender à requisição”. A seção 15.4 agrupa os códigos em quatro tipos de redirecionamento:

  • O recurso está disponível em outra URI, indicada em Location: 301, 302, 307 e 308.
  • O cliente pode escolher entre várias representações do recurso: 300.
  • Outro recurso fornece uma resposta indireta à requisição: 303.
  • O cliente pode reutilizar uma cópia que já tem em cache: 304.

Na prática, só 301, 302, 303, 307 e 308 funcionam como redirecionamentos automáticos. O Fetch Standard, que os navegadores implementam, chama exatamente esses cinco códigos de “redirect statuses” (status de redirecionamento). Um 300 costuma ser exibido como uma página normal, e um 304 diz ao cliente para usar a cópia que tem em cache.

O valor de Location pode ser uma referência relativa, como /new-page ou ../shop/. O cliente a resolve em relação à URL que acabou de requisitar (RFC 9110, seção 10.2.2). Se o Location não tiver fragmento, o fragmento original é mantido: um link para /guide#install que redireciona para /docs/guide termina em /docs/guide#install.

Para os cinco redirecionamentos de verdade, duas perguntas definem qual código usar:

  1. A mudança é permanente ou temporária? Redirecionamentos permanentes podem ser armazenados em cache e sinalizam aos mecanismos de busca que devem indexar a nova URL no lugar da antiga.
  2. O método da requisição precisa ser mantido? Isso importa quando a requisição original é um POST, PUT ou DELETE, e não um simples GET.

Visão geral dos códigos de redirecionamento

Código Nome Permanente Mantém o método Cacheável por padrão Uso típico Busca do Google
300 Multiple Choices – – Sim Oferecer representações alternativas (raro) –
301 Moved Permanently Sim Não: POST pode virar GET Sim Páginas e sites que mudaram de endereço, canonicalização de HTTPS e de host Permanente (sinal forte)
302 Found Não Não: POST pode virar GET Não Redirecionamentos de curto prazo, barreiras de login, testes A/B Temporário (sinal fraco)
303 See Other Não Não: vira GET Não Página de resultado após o envio de um formulário Temporário
304 Not Modified Não é redirecionamento – – Resposta a uma requisição condicional Conteúdo inalterado
305 Use Proxy Obsoleto – – Não use –
306 (Unused) Reservado – – Não use –
307 Temporary Redirect Não Sim Não Redirecionamentos temporários com qualquer método Temporário
308 Permanent Redirect Sim Sim Sim Mudanças permanentes de APIs, formulários ou qualquer URL Permanente (sinal forte)

“Cacheável por padrão” significa que o código é cacheável de forma heurística segundo a RFC 9110, seção 15.1: um cache pode reutilizar a resposta sem consultar o servidor, mesmo que ela não traga nenhuma informação explícita de validade. Ainda assim, qualquer resposta pode se tornar cacheável ou não cacheável com Cache-Control. A coluna do Google resume a documentação do Google sobre redirecionamentos e códigos de status HTTP.

Os códigos, um por um

300 Multiple Choices

O recurso tem várias representações, como idiomas ou formatos de arquivo diferentes, e o cliente pode escolher. O corpo da resposta costuma listar as opções, sem um formato padronizado. É possível indicar uma opção preferida em Location, mas não dá para contar com os navegadores para segui-la. Na prática, o 300 quase nunca é usado: a negociação de conteúdo no servidor ou uma página comum com links funcionam melhor.

301 Moved Permanently

O recurso de destino “recebeu uma nova URI permanente, e quaisquer referências futuras a esse recurso devem usar uma das URIs incluídas”. É o código padrão para páginas que mudaram de endereço, troca de domínio, migração de HTTP para HTTPS e redirecionamentos para o host canônico.

Dois detalhes são importantes. Um 301 é cacheável de forma heurística, então os navegadores podem memorizá-lo e parar de consultar o servidor (veja a seção sobre cache mais abaixo). E, por razões históricas, um cliente pode transformar um POST em GET ao seguir um 301, e os navegadores fazem isso. Para páginas carregadas com GET, isso não faz diferença; para um endpoint de formulário ou uma API, use 308.

302 Found

O recurso de destino “está temporariamente em uma URI diferente”, então os clientes devem continuar usando a URL original nas próximas requisições. Um 302 não é cacheável por padrão. Use-o em situações passageiras: uma página de promoção que substitui uma categoria por uma semana, uma barreira de login, roteamento geográfico ou um teste A/B.

Assim como o 301, o 302 permite a troca de POST para GET. Muitos frameworks enviam 302 quando nenhum código é informado, e é por isso que mudanças permanentes tantas vezes acabam virando 302 por acidente.

303 See Other

Um 303 leva o cliente a outro recurso, que serve de resposta indireta à requisição original, e o cliente o obtém com GET (ou HEAD). Ele foi pensado para o padrão Post/Redirect/Get: depois de processar o envio de um formulário, o servidor responde 303 com a URL de uma página de confirmação ou de resultado. Assim, recarregar essa página repete um GET inofensivo em vez de reenviar o formulário.

Um 303 não é cacheável por padrão e não afirma que a URL original mudou de endereço. O Google o trata como um 302.

304 Not Modified (não é um redirecionamento)

Apesar do número 3xx, um 304 nunca manda o cliente para lugar nenhum. Ele responde a uma requisição GET ou HEAD condicional, ou seja, uma requisição que traz If-None-Match com um valor de ETag, ou If-Modified-Since com uma data. Se o recurso não mudou, o servidor responde 304 sem corpo, e o cliente usa a cópia que tem em 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

Verificadores de redirecionamento (redirect checkers) enviam requisições incondicionais, então normalmente nunca recebem um 304. Para testar um no TrueURL, faça uma verificação normal, copie o valor de ETag dos cabeçalhos de resposta e envie-o em um cabeçalho If-None-Match em Cabeçalhos de requisição personalizados. Para o Googlebot, um 304 significa que o conteúdo não mudou desde o último rastreamento.

305 Use Proxy e 306 (Unused)

Um 305 mandava o cliente repetir a requisição pelo proxy indicado em Location. Permitir que uma resposta reconfigure o proxy de um cliente é um risco de segurança, por isso a RFC 7231 tornou o código obsoleto, e os navegadores atuais não obedecem a ele.

O 306 foi definido em uma versão anterior da especificação (muitas vezes aparece como Switch Proxy) e hoje está reservado. Nenhum dos dois códigos tem lugar em uma aplicação moderna; se você encontrar um deles, há algo mal configurado.

307 Temporary Redirect

Um 307 significa o mesmo que um 302, com um acréscimo: o cliente não pode mudar o método da requisição. Um POST é reenviado como POST, com o mesmo corpo, para a nova URL. Por isso o 307 é o redirecionamento temporário certo para APIs e endpoints de formulário.

Você também pode encontrar o 307 no DevTools do Chrome como 307 Internal Redirect de http:// para https://. O servidor nunca o enviou. Foi o próprio navegador que mudou a requisição para HTTPS, normalmente por causa de uma política HSTS que ele tem armazenada para o site.

308 Permanent Redirect

O 308 é a versão do 301 que preserva o método: permanente, cacheável de forma heurística, e o cliente não pode mudar o método. Ele foi publicado pela primeira vez em 2014, na RFC 7238, de caráter experimental, virou padrão com a RFC 7538 em 2015 e hoje faz parte da RFC 9110.

Use 308 em mudanças permanentes de endpoints que recebem requisições POST, PUT ou DELETE. Para páginas comuns, 301 e 308 são intercambiáveis, e o Google trata os dois como redirecionamentos permanentes.

Por que 303, 307 e 308 existem

A sobreposição entre 301/302 e 303/307/308 é um acidente histórico.

O HTTP/1.0 (RFC 1945, 1996) definiu o 301 Moved Permanently e o 302 Moved Temporarily. A especificação dizia que um agente de usuário não podia redirecionar um POST automaticamente sem a confirmação do usuário e já observava que alguns agentes de usuário transformavam, por engano, o POST em GET. Os navegadores continuaram fazendo isso.

O HTTP/1.1 acrescentou dois códigos que não deixam margem para interpretação. A RFC 2068 (1997) introduziu o 303 See Other, que sempre muda para GET. A RFC 2616 (1999) acrescentou o 307 Temporary Redirect, que sempre mantém o método, renomeou o 302 para Found e reconheceu abertamente que a maioria dos agentes de usuário tratava um 302 como um 303.

Mesmo assim, ainda faltava um redirecionamento permanente que mantivesse o método. O 308 preencheu essa lacuna quinze anos depois. Enquanto isso, a especificação se rendeu à realidade: desde a RFC 7231 (2014), um agente de usuário pode transformar um POST em GET depois de um 301 ou de um 302.

Hoje, os navegadores aplicam estas regras do Fetch Standard:

Status Método original Os navegadores requisitam a nova URL com
301, 302 POST GET, sem o corpo
301, 302 Qualquer outro método O mesmo método
303 Qualquer método, exceto GET e HEAD GET, sem o corpo
303 GET ou HEAD O mesmo método
307, 308 Qualquer método O mesmo método e o mesmo corpo

Só o POST é reescrito depois de um 301 ou 302; um PUT ou DELETE mantém o método. Contar com isso é arriscado, porque as bibliotecas HTTP se comportam de formas diferentes, então use 307 ou 308 sempre que o método importar.

Redirecionamentos sem código de status 3xx

Meta refresh

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

A página em si é entregue com 200 OK, e o navegador segue a tag depois de carregá-la. O número é o atraso em segundos. O Google interpreta um meta refresh instantâneo como redirecionamento permanente e um meta refresh com atraso como redirecionamento temporário.

Redirecionamentos temporizados também prejudicam a acessibilidade. As diretrizes WCAG aceitam um meta refresh instantâneo como redirecionamento do lado do cliente (técnica H76), mas consideram falha um meta refresh com atraso (F40), porque a página muda por um temporizador que o usuário não controla.

Redirecionamentos JavaScript

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

location.replace() não deixa a página que redireciona no histórico da sessão, então o botão Voltar não joga o usuário de novo no redirecionamento. Já atribuir um valor a location.href deixa a página no histórico.

O Google só executa JavaScript quando renderiza uma página, depois de rastreá-la, e a renderização pode falhar. A documentação do Google recomenda redirecionamentos JavaScript apenas quando não é possível usar redirecionamentos do lado do servidor nem meta refresh. Muitos outros robôs e ferramentas nunca executam JavaScript.

O cabeçalho Refresh

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

O cabeçalho Refresh não é definido em nenhuma RFC do HTTP. Os navegadores o aceitam há décadas, e o HTML Standard o trata como equivalente a uma tag meta refresh. Muitos clientes e ferramentas HTTP, entre eles o curl, o ignoram, então prefira um redirecionamento 3xx de verdade.

Como implementar redirecionamentos corretamente

nginx

nginx
# Toda requisição http://, em qualquer um dos hosts, vai para a URL HTTPS canônica em um único salto
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 e ssl_certificate_key como acima

    # Uma página que mudou de endereço
    location = /old-page {
        return 301 /new-page;
    }

    # Uma seção inteira, mantendo o resto do caminho
    location /blog/ {
        rewrite ^/blog/(.*)$ /articles/$1 permanent;
    }
}

$request_uri contém o caminho e a query string originais, então nada se perde. rewrite ... permanent envia um 301 e rewrite ... redirect, um 302; para 307 ou 308, use return com o código desejado.

Apache (.htaccess)

Apache
# mod_alias: uma página e uma pasta inteira (o resto do caminho é acrescentado)
Redirect 301 /old-page https://example.com/new-page
Redirect 301 /blog/ https://example.com/articles/

# mod_alias com uma expressão regular
RedirectMatch 301 ^/products/([0-9]+)\.html$ https://example.com/p/$1

# mod_rewrite: envia toda requisição http:// ou com www para https://example.com em um único salto
RewriteEngine On
RewriteCond %{HTTPS} off [OR]
RewriteCond %{HTTP_HOST} ^www\. [NC]
RewriteRule ^ https://example.com%{REQUEST_URI} [R=301,L]

Redirect sem código de status envia um 302, então sempre informe o código. Atrás de um balanceador de carga ou de uma CDN que se conecta ao Apache por HTTP simples, %{HTTPS} fica sempre como off e a última regra entra em loop; nesse caso, teste %{HTTP:X-Forwarded-Proto}. O mod_alias e o mod_rewrite funcionam de forma independente, e a ordem das linhas no arquivo não decide qual módulo roda primeiro. Uma URL que corresponde a regras dos dois módulos, como http://www.example.com/old-page neste exemplo, pode levar dois saltos; para URLs que precisam redirecionar em um único salto, mantenha todas as regras delas em um só módulo.

Cloudflare

A Cloudflare pode redirecionar na borda (edge), antes que a requisição chegue ao seu servidor:

  • Single Redirects (a seção Redirect Rules do painel) identificam requisições com uma expressão de filtro e as enviam para um destino estático ou dinâmico com 301, 302, 303, 307 ou 308. Uma regra típica do www para o domínio raiz (apex) tem estas configurações:

    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
  • Bulk Redirects cuidam de listas longas de URLs de origem e de destino, como um mapa de migração enviado em CSV. Cada entrada tem seu próprio código de status e opções como preservar a query string, e uma Bulk Redirect Rule ativa a lista.

  • Os Workers podem criar redirecionamentos em código com Response.redirect():

JavaScript
export default {
  async fetch(request) {
    const url = new URL(request.url);
    if (url.pathname === '/old-page') {
      url.pathname = '/new-page'; // a query string é mantida
      return Response.redirect(url.toString(), 301);
    }
    return fetch(request);
  },
};

Response.redirect() usa 302 por padrão e só aceita 301, 302, 303, 307 e 308. Qualquer outro código lança um RangeError.

Node.js e Express

JavaScript
import express from 'express';

const app = express();
app.set('trust proxy', 1); // quando roda atrás de um balanceador de carga ou CDN

// HTTPS e host canônico em um único salto
app.use((req, res, next) => {
  if (req.secure && req.hostname === 'example.com') return next();
  res.redirect(301, `https://example.com${req.originalUrl}`);
});

// Uma página que mudou de endereço
app.get('/old-page', (req, res) => {
  res.redirect(301, '/new-page');
});

res.redirect(url) sem status envia um 302. O Express 5 só aceita a ordem res.redirect(status, url). No Node.js puro, defina o status e o cabeçalho você mesmo:

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

PHP

PHP
<?php
// Precisa ser executado antes de qualquer saída ser enviada.
header('Location: https://example.com/new-page', true, 301);
exit;

Um cabeçalho Location sozinho faz o PHP enviar um 302 (a menos que um status 201 ou 3xx já tenha sido definido), então passe o código como terceiro argumento e use exit para que nada mais seja executado. No WordPress, wp_redirect() e wp_safe_redirect() também usam 302 por padrão; passe 301 como segundo argumento.

Como escolher o código de redirecionamento certo

  • A página mudou de endereço de vez e é uma página comum carregada com GET: use 301. Um 308 funciona igualmente bem.
  • A mudança é permanente e os clientes podem enviar POST, PUT ou DELETE, como em APIs, URLs de envio de formulários e webhooks: use 308.
  • O redirecionamento é temporário e só GET importa: use 302.
  • O redirecionamento é temporário e o método precisa ser mantido: use 307.
  • Um formulário ou outro POST foi processado e o usuário deve ver uma página de resultado: use 303.
  • HTTP para HTTPS: use 301 ou 308 e envie um cabeçalho Strict-Transport-Security a partir do site HTTPS, para que os navegadores pulem a requisição insegura da próxima vez.
  • O conteúdo não existe mais e não tem equivalente: não redirecione. Retorne 404 ou 410. O Google pode tratar um redirecionamento para uma página sem relação, como a página inicial, como soft 404.
  • 300, 305 e 306: não use. Um 304 é gerado pelo seu servidor ou cache em resposta a requisições condicionais e nunca é uma opção de redirecionamento.

A armadilha do cache com 301 e 308

Como 301 e 308 são cacheáveis de forma heurística, os navegadores podem armazená-los e deixar de consultar o servidor. Navegadores baseados no Chromium guardam um 301 ou 308 sem cabeçalhos de cache até que a entrada seja descartada ou o usuário limpe o cache. Um redirecionamento permanente errado pode continuar mandando visitantes antigos para o lugar errado muito tempo depois de você corrigir o servidor, e não há como removê-lo de forma confiável dos navegadores deles.

Para se proteger:

  • Teste regras novas primeiro com 302 ou 307 e mude para 301 ou 308 quando o destino estiver confirmado.
  • Como alternativa, limite por quanto tempo o redirecionamento permanente pode ficar em cache enquanto você o implanta:
nginx
location = /old-page {
    add_header Cache-Control "max-age=3600";
    return 301 https://example.com/new-page;
}
  • Nos testes, use uma janela anônima (ou privativa) ou o DevTools com Disable cache, ou então um verificador do lado do servidor, que nunca usa cache de navegador.
  • Limpe os caches da CDN e de proxies depois de alterar um redirecionamento, porque eles também podem armazenar redirecionamentos.
  • Não tente desfazer um redirecionamento A → B que ficou em cache redirecionando B de volta para A. Visitantes cujos navegadores guardaram o primeiro redirecionamento podem cair em um loop.

Como testar seus redirecionamentos

Seja qual for a sua plataforma, confira o resultado de fora. O Verificador de redirecionamento 301 confirma que cada salto de uma mudança permanente é um 301 ou 308, o Verificador de cadeia de redirecionamentos mostra cada salto com o Location e os cabeçalhos, e o Verificador de redirecionamento HTTPS testa as quatro variantes de protocolo e host de uma vez. O Verificador de meta refresh encontra redirecionamentos do lado do cliente que escapam às ferramentas que só leem cabeçalhos, e o Teste de latência de redirecionamento mede quanto custa cada salto.

No terminal, curl -sIL https://example.com/old-page imprime os cabeçalhos de cada salto. Nosso guia sobre como verificar redirecionamentos de URL explica em detalhes a linha de comando, o DevTools e os problemas de redirecionamento mais comuns.