Cabeçalhos HTTP e SEO: os headers que o Googlebot realmente lê

Cabeçalhos HTTP são a parte da resposta do servidor que o navegador esconde do usuário, mas que o Googlebot lê antes de tocar em qualquer linha do HTML. Eles decidem coisas que nenhuma tag on-page decide: se o rastreador precisa baixar a página de novo ou pode reaproveitar a cópia que já tem, se um arquivo que não é HTML tem uma versão canônica, e se um erro é temporário ou definitivo. Este artigo cobre seis headers que fazem esse trabalho — Last-Modified, ETag, Cache-Control, Vary, Link rel="canonical" enviado como cabeçalho (não como tag) e Retry-After — além do papel do Content-Type na indexação. O cabeçalho X-Robots-Tag tem um artigo dedicado só para ele e não é repetido aqui.
Vale separar duas coisas que costumam se misturar: rastreamento, que é o Googlebot baixando a página, e indexação, que é o Google decidindo guardar e servir essa versão nos resultados. A maioria dos cabeçalhos abaixo atua na primeira etapa — controlam quanto trabalho o rastreador precisa fazer e como ele decide se vale a pena baixar de novo —, mas dois deles, o canonical em header e o Content-Type, têm efeito direto na segunda.
Last-Modified: a data que economiza rastreamento
O cabeçalho Last-Modified informa a data e a hora da última alteração real do conteúdo e viabiliza a requisição condicional: o Googlebot guarda essa data e, na próxima visita, envia um If-Modified-Since com o mesmo valor. Se o servidor confirma que nada mudou, responde com 304 Not Modified e corpo vazio — o rastreador economiza o tempo de baixar o HTML inteiro, e esse tempo sobra para outras URLs do mesmo site.
O problema recorrente é o Last-Modified que mente. Muitos CMSs preenchem esse valor com o horário do último deploy da aplicação, não com o da última edição do conteúdo daquela página. O resultado é que todas as páginas trocam de Last-Modified no mesmo segundo, mesmo as que não mudaram há meses. Depois de observar isso algumas vezes, o rastreador aprende que o cabeçalho não é confiável naquele domínio e passa a dar menos peso a ele, o que anula a economia que deveria trazer. A decisão correta é calcular o valor a partir do campo de atualização do próprio registro de conteúdo — o updated_at da postagem ou do produto —, não a partir de um timestamp global de build.
- Páginas geradas dinamicamente a cada requisição devem calcular Last-Modified a partir do dado subjacente, não omiti-lo por padrão
- Um Last-Modified que muda em todo deploy, mesmo sem alteração de conteúdo, ensina o rastreador a ignorar o cabeçalho
ETag e requisições condicionais
O ETag é uma impressão digital opaca de uma representação específica do recurso — normalmente um hash do conteúdo. Ele resolve uma limitação do Last-Modified: duas mudanças podem acontecer dentro do mesmo segundo, e um relógio não distingue isso, mas um hash de conteúdo sim. O fluxo é parecido: o rastreador guarda o ETag recebido, manda de volta um If-None-Match na próxima visita e, se o valor bate, o servidor responde 304 sem reenviar o corpo. Existem ETags fortes e fracos (prefixo W/); o fraco aceita que a representação seja semanticamente equivalente mesmo com bytes diferentes.
O erro mais comum não é de conceito, é de infraestrutura: em ambientes com vários servidores atrás de um balanceador, alguns geradores de ETag usam metadados do arquivo no disco de cada instância — como o inode — em vez de um hash do conteúdo. O mesmo HTML, servido por duas instâncias diferentes, produz dois ETags diferentes. Toda visita cai numa instância diferente, o If-None-Match nunca bate, e a página nunca recebe um 304 — é sempre baixada por completo, mesmo sem ter mudado uma linha. A correção é gerar o ETag a partir de um hash do conteúdo renderizado, nunca de metadados específicos da máquina que respondeu.
Cache-Control: não é só para o navegador
Cache-Control define por quanto tempo (max-age), onde (public ou private) e se uma resposta pode ser armazenada (no-store) ou precisa ser revalidada antes de reutilizada (no-cache, que apesar do nome permite cache, só exige revalidação). É fácil confundir isso com controle de indexação, mas Cache-Control não diz ao Google para não indexar nada — essa instrução é do meta robots ou do X-Robots-Tag. O que ele controla é quem pode reaproveitar a resposta e por quanto tempo, e isso afeta o SEO por dois caminhos indiretos.
O primeiro é a renderização. Quando o Google precisa executar JavaScript para montar a página, ele busca CSS e JS como recursos separados. Um Cache-Control mal configurado nesses arquivos — no-store em bundles que não mudam entre deploys, por exemplo — força buscar tudo de novo a cada passagem, o que soma latência e pode empurrar a renderização para uma fila secundária em sites grandes. O segundo é a disponibilidade: CDNs usam Cache-Control, junto com diretivas como stale-while-revalidate, para decidir se servem uma cópia levemente desatualizada quando a origem está fora do ar, em vez de devolver um erro que o Googlebot interpreta como o site indisponível.
- no-store em CSS e JS estáticos gera reprocessamento desnecessário a cada rastreamento
- Cache-Control não substitui noindex — controla armazenamento, não indexação
- Endpoints que alimentam conteúdo renderizado no cliente também precisam de uma política de cache coerente, não apenas o HTML final
Vary: o cabeçalho que ninguém lê até dar problema
Vary diz a qualquer cache — CDN, proxy, ou o cache interno de um navegador — quais cabeçalhos da requisição podem mudar a resposta. Vary: Accept-Language sinaliza que o conteúdo muda por idioma; Vary: Cookie, que muda por usuário logado; Vary: User-Agent, que muda por dispositivo. O problema aparece quando o servidor varia a resposta por um critério que não está declarado no Vary: a CDN guarda uma cópia única com uma chave de cache que não considera aquele critério, e passa a servir a versão errada para visitantes diferentes — inclusive para o próprio Googlebot.
O caso mais relevante para SEO é a segmentação por idioma via Accept-Language na mesma URL, em vez de URLs distintas. Mesmo com Vary: Accept-Language declarado, o rastreador costuma requisitar com um cabeçalho de idioma fixo e não varia por região como um navegador real faria, então só vê e indexa uma das versões — geralmente a padrão. É por isso que a orientação prática para conteúdo multilíngue é usar URLs próprias por idioma em vez de negociação de conteúdo por cabeçalho, e usar hreflang para conectá-las. Outro efeito colateral comum é Vary: Cookie declarado em páginas que não deveriam variar por cookie nenhum — toda requisição gera uma chave de cache única, a CDN para de cachear a página, e o tempo de resposta sobe para todo mundo.
Link rel="canonical" enviado como cabeçalho HTTP
A forma mais conhecida de declarar uma URL canônica é a tag <link rel="canonical"> dentro do <head> do HTML. Mas essa tag só existe onde há um <head> — e um PDF ou uma imagem não tem HTML nenhum. Para esses recursos, o canonical é enviado como cabeçalho HTTP, no formato Link: <https://exemplo.com/arquivo.pdf>; rel="canonical". É a única forma de apontar a versão preferida quando o mesmo PDF fica acessível por várias URLs — uma servida por um CDN com parâmetros de campanha, outra limpa no domínio principal.
Quando existem os dois — um canonical em header e uma tag na mesma URL — e apontam para destinos diferentes, o Google recebe sinais conflitantes sobre a mesma página. A documentação de canonicalização é clara em um ponto: vários sinais consistentes entre si reforçam a escolha de uma URL canônica; sinais que se contradizem enfraquecem a confiança em qualquer um deles, em vez de simplesmente um vencer o outro por prioridade de origem. Na prática, o canonical em header deve ser gerado a partir da mesma fonte de verdade que a tag, nunca hardcoded separadamente por uma camada de CDN que não sabe o que a aplicação declara no HTML.
Retry-After: como sinalizar um problema temporário sem parecer permanente
Retry-After acompanha respostas 503 (Service Unavailable) ou 429 (Too Many Requests) e diz ao rastreador quanto tempo esperar antes de tentar de novo — em segundos ou como uma data. É o jeito correto de tirar uma seção do site do ar durante uma migração ou uma sobrecarga pontual, porque comunica algo que um simples 500 ou uma página de erro com status 200 não comunicam: que a ausência é temporária, e por quanto tempo o rastreador deveria adiar a próxima tentativa.
O erro mais caro aqui é de duração, não de sintaxe. Um 503 com Retry-After apontando para dentro de algumas horas, usado durante uma manutenção real, é tratado como o que é: uma pausa. O mesmo 503 mantido por dias, renovado repetidamente com um novo Retry-After curto a cada vez que o anterior expira, deixa de parecer temporário — passagens de erro sustentadas por muito tempo sinalizam que a página deixou de existir, e a tendência é o conteúdo ser removido do índice como qualquer URL persistentemente indisponível. Outro deslize é colocar Retry-After numa resposta 200: o cabeçalho não tem efeito fora do contexto de erro temporário e é simplesmente ignorado.
- Use 503 mais Retry-After para manutenções reais, nunca um 200 com mensagem de erro no corpo
- Retry-After longo demais, renovado indefinidamente, tende a ser lido como remoção, não como pausa
- Retry-After em respostas 200 não tem efeito e é ignorado
Content-Type: o header que decide se a página existe para o Google
Content-Type diz ao rastreador como interpretar os bytes que estão chegando: text/html para uma página normal, application/pdf para um documento, application/json para uma resposta de API. Parece um detalhe de infraestrutura, mas é a diferença entre o Google tratar a URL como uma página a ser analisada e renderizada, ou como um arquivo para download. Uma rota pensada para servir HTML que, por erro de configuração, responde com application/octet-stream não é interpretada como documento — ela não é indexada como página de conteúdo, mesmo que o HTML dentro do corpo esteja perfeito.
Isso acontece com frequência em duas situações: funções serverless que esquecem de definir o Content-Type e caem num valor padrão genérico do runtime, e servidores de arquivo estático configurados para uma extensão que não corresponde ao conteúdo real. Vale conferir também o charset declarado dentro do Content-Type, como charset=utf-8 — uma divergência entre esse charset e o declarado numa meta tag dentro do HTML pode fazer o texto ser interpretado com a codificação errada durante a extração de título e descrição, o que aparece como acentuação corrompida no resultado de busca.
Como conferir os cabeçalhos que o seu servidor está enviando
Os cabeçalhos são públicos e fáceis de inspecionar sem ferramenta especial. O comando curl -I seguido da URL mostra só os cabeçalhos da resposta, sem baixar o corpo — a forma mais rápida de checar Last-Modified, ETag, Cache-Control e Content-Type de uma vez. A aba de rede das ferramentas de desenvolvedor do navegador mostra a mesma informação por requisição, incluindo Vary e qualquer Link enviado como header.
O ponto prático é comparar: pegar duas ou três URLs representativas do site — uma página de conteúdo, um arquivo estático como CSS, e um recurso que não é HTML, se existir — e olhar os cabeçalhos lado a lado. Divergências entre URLs que deveriam se comportar de forma parecida costumam apontar para uma camada específica da stack, como um CDN ou uma função serverless, que está reescrevendo ou omitindo cabeçalhos sem que o time de conteúdo saiba.
- curl -I <url> mostra os cabeçalhos sem baixar o corpo da página
- A aba de rede do navegador mostra os mesmos cabeçalhos por requisição, incluindo Vary e Link
Perguntas frequentes
O Cache-Control substitui o meta robots para bloquear indexação?
Não. Cache-Control só controla armazenamento e reuso de cache; quem controla indexação é o meta robots ou o X-Robots-Tag. Uma página pode ter no-store e ainda ser indexada normalmente.
Um ETag diferente a cada visita é sempre um problema?
É um problema quando o conteúdo não mudou de verdade. Se o gerador de ETag usa metadados da instância do servidor em vez de um hash do conteúdo, o rastreador nunca recebe um 304 e sempre rebaixa a página inteira, mesmo sem alteração real.
Faz sentido usar Retry-After em vez de simplesmente devolver um redirecionamento temporário?
Depende do motivo. Retry-After com 503 é para indisponibilidade real e temporária, como uma manutenção. Um redirecionamento serve para apontar para outra URL que já responde normalmente; não são soluções para o mesmo problema.
O canonical em cabeçalho HTTP tem mais prioridade do que a tag na página?
Não existe uma prioridade fixa de um sobre o outro. Os dois são sinais que o Google tenta consolidar; quando concordam, reforçam a escolha da URL canônica, e quando divergem, a confiança no sinal cai em vez de um simplesmente vencer o outro.