Entendendo os códigos HTTP entre 300 e 400: guia prático
Os status codes na faixa de 300 a 400 do HTTP representam dois grupos diferentes que muita gente mistura sem necessidade. De 300 a 399 são redirecionamentos, que indicam que o recurso solicitado está disponível em outro lugar. De 400 a 499 são erros do cliente, que significam que algo na requisição está errado. Quando você tá lidando com isso no dia a dia, saber a diferença entre eles evita dor de cabeça na hora de depurar APIs e configurações de servidor. O problema é que a maioria dos tutoriais ensina isso de forma genérica, sem mostrar o que acontece quando você coloca na prática. E às vezes o que parece simples vira um pesadelo de cabeçalhos mal configurados.
Como funcionam os numerais de 300 a 400 no HTTP
Vamos começar com os redirecionamentos 3xx, porque é onde as pessoas mais erram. O código 301 (Moved Permanently) é o mais conhecido, mas também o mais mal compreendido. Quando você devolve um 301, o navegador e os caches intermediários entendem que aquele redirecionamento é permanente. Isso significa que, daqui pra frente, todo mundo vai usar a nova URL direto, sem consultar a antiga. O problema é que isso se aplica também a requisições POST, PUT, DELETE e outras. Muitos frameworks convertem automaticamente requisições POST em GET ao seguir um 301, o que quebra lógica de negócio que depende do método original. O 302 (Found) é o redirecionamento temporário clássico. A especificação original diz que ele deve preservar o método da requisição, mas na prática os navegadores tratam o 302 como um 301 na maior parte dos casos. É por isso que ele se tornou praticamente sinônimo de "redireciona mas não cacheia". Se você quer ser preciso, o 307 (Temporary Redirect) é o equivalente moderno do 302, porque garante que o método e o corpo da requisição sejam mantidos.
Já o 308 (Permanent Redirect) foi introduzido justamente para resolver o problema do 301 que eu mencionei. Ele preserva o método da requisição original de forma explícita. Se alguém fez um POST para a URL antiga, o cliente vai fazer um POST para a nova URL. Isso faz diferença real em APIs que usam métodos não seguros, como PUT e DELETE. No meu trabalho, eu tive um caso específico onde um cliente migrou um sistema de e-commerce inteiro e configurou todos os redirecionamentos como 301. Quando o checkout chamava o endpoint de pagamento com POST, o gateway simplesmente transformava a requisição em GET e o pagamento falhava. O erro era intermitente e só acontecia porque o navegador ou a biblioteca HTTP estava seguindo o redirecionamento antes de enviar o corpo. A solução foi trocar todos os 301 por 308 nos endpoints que recebiam requisições POST, e manter o 301 apenas nas URLs de conteúdo que realmente não recebem corpo. Levou cerca de 4 horas para ajustar as regras no nginx e testar todos os fluxos.
Agora vamos para os erros 4xx. O 400 (Bad Request) é genérico demais na maioria das implementações. O ideal é que ele inclua uma mensagem explicando o que está errado, mas quase ninguém faz isso direito. O 401 (Unauthorized) e o 403 (Forbidden) também são confundidos frequentemente. O 401 significa que o cliente não se identificou — o servidor não sabe quem é. O 403 significa que o servidor sabe quem é, mas nega o acesso. Um erro comum é devolver 403 quando o token JWT expirou, quando na verdade deveria ser 401, porque o problema não é permissão, é autenticação ausente ou inválida. O 404 (Not Found) tem sua própria armadilha. Muita gente usa 404 para tudo que não encontra, incluindo recursos que existem mas que o usuário atual não tem permissão para ver. O correto nesse caso é 404 mesmo, porque você não quer vazar informação sobre a existência do recurso. Mas se o recurso não existe de fato, um 410 (Gone) é mais informativo do que um 404, porque diz explicitamente que aquele recurso já esteve disponível e não estará mais.
Outro detalhe importante que pouca gente menciona: o 405 (Method Not Allowed) exige o cabeçalho Allow na resposta. Sem ele, o cliente não sabe quais métodos são aceitos para aquele recurso. Eu vi servidor que devolvia 405 mas esquecia de colocar o Allow, e isso quebrava ferramentas de teste automático que dependiam do cabeçalho para construir listas de métodos válidos.
👉 Clique no botão abaixo para saber mais sobre o assunto!
Dicas práticas que você não encontra em documentação oficial
Primeiro, testar redirecionamentos com curl usando a flag -I mostra o cabeçalho Location sem seguir o redirecionamento automaticamente. Com -L você segue até o final, mas perde a visibilidade dos status intermediários. O ideal é usar --max-redirs 5 para evitar loops infinitos e ver exatamente quantos saltos a URL faz antes de chegar ao destino. Segundo, se você trabalha com APIs REST, configure o servidor para devolver 307 ou 308 em vez de 301 ou 302 quando houver mudança de URL. A maioria dos frameworks modernos já suporta esses códigos nativamente, então não há motivo para ficar no legado.
Terceiro, para os 4xx, padronize a resposta de erro com um corpo JSON consistente que inclua código, mensagem e campos específicos do erro. Isso facilita a vida de quem consome a API e reduz o tempo de debugging. Em vez de só devolver o status code, inclua informações como qual campo falhou na validação ou qual permissão específica estava faltando. Isso transforma um 400 genérico em algo útil. Um limite importante: esses status codes funcionam bem quando o servidor controla todo o fluxo, mas em arquiteturas distribuídas com gateways, load balancers e CDNs, o comportamento pode variar. Alguns CDNs interceptam 3xx respostas e substituem pelo próprio cache, ignorando o cabeçalho Location. Nesse cenário, o ideal é configurar o CDN explicitamente para passar os status codes através sem modificação, ou usar um padrão de proxy reverso que não interfira nessa camada.
Se o seu cenário envolve muitas migrações de URL com manutenção de métodos HTTP, considere usar o 308 em todos os redirecionamentos permanentes, independente do método. A compatibilidade com navegadores antigos não é mais um problema significativo em 2024, e a segurança de manter o método correto vale mais do que suportar um navegador de 2010 que ninguém usa mais.
Exemplo de configuração no nginx
Para configurar um redirecionamento permanente que preserva o método, a diretiva no nginx é simples:
return 308 https://novo-dominio.com/nova-url;
Para os erros 4xx, o nginx já devolve respostas adequadas por padrão, mas você pode customizar o corpo com error_page. Isso permite adicionar contexto específico ao invés de depender da página de erro genérica que vem por padrão. A faixa de 300 a 400 no HTTP não é difícil de dominar quando você entende a intenção por trás de cada código. O erro mais comum é tratar todos os redirecionamentos como iguais e todos os erros do cliente como a mesma coisa. Quando você para de generalizar e olha o que cada status representa na prática, as decisões de configuração ficam muito mais claras.