Cabeçalhos HTTP Cache-Control O que realmente significam no-cache, no-store e max-age
Uma explicação prática dos diretivas Cache-Control — o que os navegadores e CDNs realmente fazem com no-cache, no-store, max-age, s-maxage e ETags. Incluindo os erros que mais desenvolvedores enfrentam em produção.
Provavelmente já escreveu Cache-Control: no-cache e assumiu que o navegador ignoraria completamente a cache. Isso não acontece. no-cache significa "revalidar antes de servir da cache". Se você realmente deseja que a resposta nunca seja armazenada, isso é no-store. Essa confusão causa bugs com dados obsoletos em produção, que são difíceis de diagnosticar porque tudo parece normal na aba Network na primeira carga.
Vamos analisar cada diretiva claramente — o que os navegadores fazem com ela, o que os CDNs fazem com ela, e os erros que surgem ao misturá-las.
Referência completa das diretivas Cache-Control
| Diretiva | Comportamento do navegador | Comportamento do CDN / proxy | Uso típico |
|---|---|---|---|
max-age=N | Cache por N segundos | Cache por N segundos (a menos que s-maxage sobreponha) | Ativos estáticos, respostas de API |
s-maxage=N | Ignorado | Cache por N segundos | TTL do CDN separado do navegador |
no-cache | Cache, mas revalida em cada requisição | Cache, mas revalida em cada requisição | Conteúdo que muda frequentemente com ETags |
no-store | Não armazene em nenhum lugar | Não armazene em nenhum lugar | Respostas de autenticação, dados sensíveis do usuário |
must-revalidate | Sem entrega de dados obsoletos — revalide ou falhe | Sem entrega de dados obsoletos — revalide ou falhe | Respostas de API onde dados obsoletos são quebrantes |
proxy-revalidate | Ignorado | Sem entrega de dados obsoletos em caches compartilhados | Obrigatório revalidação no CDN específico |
private | O navegador pode armazenar | Não deve ser armazenado | Páginas específicas do usuário |
public | Qualquer cache pode armazenar | Pode ser armazenado | Recursos estáticos compartilhados |
immutable | Nunca revalida dentro do max-age | Varia conforme o CDN | Ativos hashados / versionados |
stale-while-revalidate=N | Serve dados obsoletos por N segundos enquanto obtém a versão atual | Varia conforme o CDN | Velocidade sem rigidez de dados obsoletos |
max-age e s-maxage: TTL do navegador vs TTL do CDN
max-age=N informa tanto o navegador quanto os CDNs quanto segundos a resposta é fresca. Após N segundos, a resposta armazenada se torna obsoleta e deve ser revalidada antes de ser usada.
s-maxage=N é exclusiva do CDN. Os navegadores a ignoram completamente. Se você deseja que o CDN armazene por uma hora mas o navegador apenas por 5 minutos:
Cache-Control: max-age=300, s-maxage=3600
O navegador armazena por 5 minutos. CloudFront, Fastly, Nginx — eles usam 3600 segundos. Uma armadilha comum: definir max-age=0 achando que desativa a cache. Isso não acontece. max-age=0 significa que a resposta se torna imediatamente obsoleta — o navegador ainda a armazena e revalida, provavelmente obtendo um 304. Se você nunca quiser que ela seja armazenada, use no-store.
no-cache: O que você realmente pensa
Cache-Control: no-cache não significa "não use a cache". Significa "não sirva da cache sem revalidar com o servidor primeiro".
A sequência quando um navegador tem uma resposta armazenada com no-cache:
- Um novo pedido chega para o recurso armazenado
- O navegador envia o ETag da resposta armazenada (via
If-None-Match) ou o timestamp de modificação para o servidor - Se o conteúdo não mudou → o servidor retorna
304 Not Modifiedsem corpo - Se o conteúdo mudou → o servidor retorna
200 OKcom a nova resposta
A vantagem em relação a no-store: você ainda obtém as economias de banda das respostas 304. Se seu conteúdo muda ocasionalmente, mas precisa ser fresco quando muda, no-cache combinado com um ETag é a combinação correta.
no-store: O verdadeiro "não armazene isso"
Cache-Control: no-store significa que a resposta não deve ser armazenada em nenhum lugar — nem no cache do navegador, nem no CDN, nem em um proxy intermediário. Nenhuma cópia, ponto final.
Use isso para:
- Respostas de autenticação (tokens de login, dados de sessão)
- Dados pessoais sensíveis
- Conteúdo de uso único (confirmações de pagamento, páginas de OTP)
Um detalhe subtis: no-store não impede que uma página apareça no cache de navegação do navegador (bfcache). Os navegadores mantêm uma cópia em memória para melhorar o desempenho de navegação, separada do cache HTTP. Se você precisa lidar com problemas do botão voltar após logout, integre o evento pageshow e verifique event.persisted.
must-revalidate: Sem graça de dados obsoletos
Os specs de cache HTTP permitem que os caches sirvam respostas obsoletas quando o servidor original não está acessível — uma característica de resiliência que a maioria dos desenvolvedores não conhece. must-revalidate remove essa margem: uma vez que uma resposta armazenada se torna obsoleta, o cache deve revalidar ou retornar um 504. Sem entrega de dados obsoletos em qualquer circunstância.
# Without must-revalidate: CDN may serve stale if origin is slow or down
Cache-Control: max-age=3600
# With must-revalidate: stale = error, not a fallback
Cache-Control: max-age=3600, must-revalidate
Use isso para respostas de API onde a entrega de dados obsoletos quebra a funcionalidade — contagens de estoque, preços, estado de autenticação — e não apenas fazem parecer um pouco errado.
private vs public: O erro do CDN que revela dados do usuário
private significa que a resposta é destinada a um usuário específico. Os navegadores podem armazená-la; os caches compartilhados (CDNs, proxies reversos) não devem.
public permite que qualquer cache — incluindo os compartilhados — armazene a resposta. Alguns caches só armazenam respostas de requisições autenticadas se você marcar explicitamente public.
O erro real no mundo real: um desenvolvedor copia e cola Cache-Control: public, max-age=3600 de um ativo estático para uma página que inclui dados específicos do usuário. O CDN armazena a resposta. O usuário B faz o mesmo pedido e recebe a página do usuário A do cache. Isso não é teórico — o GitHub teve uma variante disso em 2018. Marque respostas autenticadas ou específicas do usuário private explicitamente, mesmo que você pense que seu CDN "sabe" que não deve armazená-las.
ETags e requisições condicionais
ETags são como o servidor diz "aqui está um rastro dessa resposta". O navegador armazena o ETag junto com a resposta armazenada e envia-o de volta na próxima requisição via If-None-Match. Se o conteúdo não mudou, o servidor retorna 304 Not Modified sem corpo — mesma enquadramento de frescor que no-cache, com muito menos uso de banda.
O no-cache + Fluxo de ETag:
→ GET /api/config HTTP/1.1
← HTTP/1.1 200 OK
Cache-Control: no-cache
ETag: "abc123"
[full response body]
→ GET /api/config HTTP/1.1
If-None-Match: "abc123"
← HTTP/1.1 304 Not Modified
[no body — browser uses its cached copy]
Dois tipos de ETag:
- ETags fortes (
"abc123") — identicamente byte a byte. Obrigatório para suporte de requisições de intervalo no CDN. - ETags fracos (
W/"abc123") — semanticamente equivalentes, mas não necessariamente identicamente byte a byte. Adequado para revalidação no navegador, não para requisições de intervalo.
O Nginx gera ETags automaticamente com base no tempo de modificação e no tamanho do arquivo. O Express não os adiciona por padrão — use app.set('etag', 'strong') ou o etag middleware explicitamente.
Last-Modified e If-Modified-Since
Mesmo conceito que ETags, mas mais grosseiro — baseado em timestamp em vez de hash de conteúdo. O servidor inclui Last-Modified; o navegador envia If-Modified-Since em requisições subsequentes.
O problema: se você redeploys e os tempos de modificação dos arquivos atualizam mesmo que o conteúdo não tenha mudado, os caches invalidam desnecessariamente. Um hash de conteúdo baseado em ETag não terá esse problema. Use ETags sempre que possível e trate Last-Modified como um fallback para servidores que não suportam ETags.
Vary: O cabeçalho que multiplica silenciosamente seu cache
O Vary cabeçalho informa aos caches que a resposta pode diferir com base em outros cabeçalhos de requisição. Cada combinação única desses cabeçalhos recebe sua própria entrada de cache.
Vary: Accept-Encoding
Isso informa aos caches para armazenar respostas separadas para gzip, brotli e codificação identidade. Correto e comum. O perigoso: Vary: Cookie. Cada usuário tem um cookie único definido, então cada usuário recebe sua própria entrada de cache — efetivamente desativando o cache compartilhado. Muitas frameworks adicionam Vary: Cookie silenciosamente. Se a taxa de acerto do seu cache do CDN for inexplicavelmente baixa, apesar de valores generosos de max-age , verifique seus cabeçalhos de resposta por Vary: Cookie que estão sendo inseridos do middleware de sessão.
Vary: * significa "não armazene isso em absoluto" na prática — cada requisição é tratada como única. É equivalente a no-store para CDNs.
Forçando atualização com parâmetros de consulta
Quando você precisa forçar downloads frescos de ativos versados, anexar um parâmetro de consulta é a abordagem padrão — a string de consulta faz parte da URL, então é tratada como um novo recurso tanto pelo navegador quanto pelos CDNs:
/app.js?v=2.1.4
/styles.css?hash=a1b2c3d4e5f6
Se você estiver construindo parâmetros de forçamento de cache dinamicamente a partir de hashes de conteúdo ou strings de versão que podem conter caracteres especiais, certifique-se de codificar porcentualmente antes de anexar. O Codificador/Decodificador de URL trata isso rapidamente se você estiver testando ou construindo URLs manualmente.
Três erros que afetam a maioria dos desenvolvedores
1. Usar no-cache quando quer no-store. Se você está lidando com respostas de autenticação, endpoints de logout ou qualquer coisa com dados pessoais sensíveis (PII), você quer no-store. no-cache deixa os dados no cache do navegador (apenas marcados como obsoletos); no-store remove completamente o rastro. A diferença importa quando os usuários compartilham dispositivos.
2. Não definir s-maxage para controle do CDN. Baixar como .yml s-maxage, seu CDN usa max-age. Se max-age é curto para a frescor do navegador (por exemplo, 60 segundos), seu CDN armazena por 60 segundos também — o que provavelmente não é o que você deseja. Separe explicitamente os dois TTLs.
3. usar public em endpoints que retornam dados do usuário. Esse é o incidente de segurança, não apenas um erro de desempenho. Qualquer resposta personalizada ou autenticada deve ser private. Default para private e opte por public apenas para recursos realmente compartilhados.
Montando Tudo
O modelo mental: no-cache é sobre a enquadramento de frescor — a resposta vive no cache, mas precisa de uma aprovação do servidor antes de ser usada. no-store é sobre deixar nenhuma rastro. max-age é o TTL do seu navegador. s-maxage é o TTL separado do seu CDN. ETags são o que tornam a revalidação barata.
Obtenha a private/public diferença certa em qualquer endpoint que toca dados do usuário. Esse único erro — copiar um cabeçalho de cache de um ativo estático para um endpoint autenticado — é o que se transforma em vazamento de dados entre usuários quando o seu CDN começa a armazenar respostas personalizadas.
Instale nossas extensões
Adicione ferramentas de IO ao seu navegador favorito para acesso instantâneo e pesquisa mais rápida
恵 O placar chegou!
Placar é uma forma divertida de acompanhar seus jogos, todos os dados são armazenados em seu navegador. Mais recursos serão lançados em breve!
Ferramentas essenciais
Ver tudo Novas chegadas
Ver tudoAtualizar: Nosso ferramenta mais recente foi adicionado em 18 de junho de 2026
