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:
- 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.
- 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.
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=3600Verificadores 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
<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
<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/1.1 200 OK
Content-Type: text/html
Refresh: 0; url=https://example.com/new-pageO 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
# 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)
# 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: OnBulk 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():
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
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:
res.writeHead(301, { Location: 'https://example.com/new-page' });
res.end();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-Securitya 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:
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.