Otimização de Desempenho com Cache-Control no Nginx

A gestão eficaz do cache é crucial para a performance de websites e aplicações. O cabeçalho HTTP Cache-Control é a ferramenta principal para instruri navegadores e proxies sobre como e por quanto tempo os recursos devem ser armazenados em cache. Este guia detalha as diretivas do Cache-Control e como configurá-las no Nginx para otimizar a experiência do utilizador.

Entendendo o Cache-Control

Cache-Control substituiu o cabeçalho Expires do HTTP/1.0 e oferece um controle mais granular sobre o comportamento do cache. As suas diretivas podem ser agrupadas em:

Diretivas de Armazenamento (Quem pode cachear)

  • public: Permite que qualquer cache (incluindo CDNs e proxies) armazene a resposta.
  • private: Indica que a resposta é específica para um utilizador e só deve ser armazenada no cache do navegador do utilizador.
  • no-cache: Permite o armazenamento em cache, mas força o cliente a revalidar a cópia em cache com o servidor antes de a usar.
  • no-store: A diretiva mais restritiva, impede que qualquer cache armazene a resposta.

Diretivas de Tempo (Por quanto tempo cachear)

  • max-age=<segundos>: Define o tempo máximo de vida do recurso em cache, em segundos, a partir do momento da requisição.
  • s-maxage=<segundos>: Similar ao max-age, mas aplica-se a caches partilhados (proxies, CDNs).
  • Expires: <data>: Cabeçalho HTTP/1.0 mais antigo, define uma data e hora de expiração absoluta.

Diretivas de Revalidação e Validação

  • must-revalidate: O cache deve verificar com o servidor se o recurso ainda é válido após a expiração.
  • proxy-revalidate: Similar ao must-revalidate, mas aplica-se apenas a caches partilhados.
  • immutable: Indica que o recurso nunca mudará durante a sua URL. Útil para recursos com nomes de ficheiro baseados em hash.

Configuração de Cache no Nginx

O Nginx permite definir cabeçalhos Cache-Control para diferentes localizações (blocos location) no seu ficheiro de configuração.

Exemplos de Configuração

Recursos Estáticos de Longo Prazo (Imagens, CSS, JS com hash)

Para recursos que raramente mudam e são referenciados por um nome de ficheiro que inclui um hash (gerado por ferramentas como Webpack ou Vite), podemos definir um cache de longa duração:


location ~* \.(js|css|png|jpg|jpeg|gif|ico|webp|svg|mjs|wasm)$ {
   # Cache por um ano (31536000 segundos)
   # immutable: o conteúdo não mudará para esta URL
   add_header Cache-Control "public, immutable, max-age=31536000";
   access_log off; # Opcional: não registrar logs para estes ficheiros
   expires 1y;     # Diretiva curta para compatibilidade com alguns caches mais antigos
}
   

Recursos de Conteúdo Dinâmico ou Personalizado

Para APIs ou páginas que podem conter dados específicos do utilizador, o cache deve ser mais restrito:


location /api/ {
   # Apenas o navegador do utilizador pode cachear, por 5 minutos
   add_header Cache-Control "private, max-age=300";
}

location /user/profile {
   # Cache curto, mas revalidar sempre após expirar
   add_header Cache-Control "private, max-age=60, must-revalidate";
}
   

Páginas HTML

Páginas HTML frequentemente contêm conteúdo que precisa ser atualizado. Um cache agressivo pode levar à exibição de conteúdo desatualizado.


location ~* \.html$ {
   # Não cachear ou cachear por um tempo muito curto e revalidar
   add_header Cache-Control "no-cache, max-age=0";
   # Pragma é um cabeçalho HTTP/1.0 legado, mas pode ajudar com caches mais antigos
   add_header Pragma "no-cache";
   add_header Expires "0";
}
   

Dados Sensíveis

Informações sensíveis nunca devem ser cacheado.


location /secure/data {
   # A diretiva mais restritiva
   add_header Cache-Control "no-store, no-cache, must-revalidate";
   add_header Pragma "no-cache";
   add_header Expires "0";
}
   

Mecanismos de Validação de Cache

Quando o Cache-Control não impede a validação (como com no-cache ou após o max-age expirar), o servidor pode usar cabeçalhos de validação para informar ao cliente se o recurso em cache ainda é válido:

  • ETag (Entity Tag): Um identificador único para uma versão específica de um recurso. O cliente envia o ETag no cabeçalho If-None-Match. Se o ETag corresponder, o servidor responde com 304 Not Modified.
  • Last-Modified: A data e hora em que o recurso foi modificado pela última vez. O cliente envia esta data no cabeçalho If-Modified-Since. Se o recurso não foi modificado desde essa data, o servidor responde com 304 Not Modified.

O Nginx pode ser configurado para gerar ou verificar estes cabeçalhos:


location ~* \.(js|css)$ {
   # ETag é geralmente ativado por padrão se o módulo http_etag estiver habilitado
   etag on;

   # Last-Modified é ativado por padrão para ficheiros estáticos
   # O Nginx usa o tempo de modificação do ficheiro

   expires 1y;
   add_header Cache-Control "public, immutable";
}
   

Estratégias Avançadas e Boas Práticas

Cacheamento por Camadas

  • Recursos versionados (com hash): Cache permanente com immutable e max-age longo (ex: 1 ano).
  • Imagens e mídia: Cache de longa duração (ex: 30 dias ou 1 ano), permitindo revalidação.
  • HTML: Sem cache ou cache muito curto com revalidação obrigatória.
  • APIs dinâmicas: Cache privado com max-age curto e must-revalidate.

Diretivas Modernas

stale-while-revalidate e stale-if-error podem melhorar a experiência do utilizador em casos de falha de rede ou revalidação:


location ~* \.(js|css)$ {
   add_header Cache-Control "public, max-age=31536000, immutable, stale-while-revalidate=86400";
   add_header Cache-Control "stale-if-error=604800";
}
   

stale-while-revalidate=86400: Permite que o cache seja usado por até 1 dia após a expiração, enquanto uma nova cópia é buscada em segundo plano.

stale-if-error=604800: Se ocorrer um erro ao tentar revalidar o cache, permite usar a cópia em cache por até 1 semana.

Debug e Moniotramento de Cache

Adicionar cabeçalhos personalizados no Nginx pode ajudar a depurar o comportamento do cache:


location / {
   # Exibe o status do cache do proxy (se proxy_cache estiver ativo)
   add_header X-Cache-Status $upstream_cache_status;

   # Exibe o cabeçalho Cache-Control enviado na resposta
   add_header X-Sent-Cache-Control $sent_http_cache_control;

   # Exibe o ETag enviado na resposta
   add_header X-Sent-ETag $sent_http_etag;

   # Outras informações úteis
   add_header X-Request-URI $request_uri;
   add_header X-File-Path $request_filename;
}
   

Variáveis comuns para depuração:

  • $upstream_cache_status: Indica se um recurso foi HIT, MISS, EXPIRED, etc. (válido apenas com proxy_cache).
  • $sent_http_cache_control: O valor do cabeçalho Cache-Control enviado na resposta.
  • $sent_http_etag: O valor do cabeçalho ETag enviado na resposta.
  • $sent_http_last_modified: O valor do cabeçalho Last-Modified enviado na resposta.

Verificação do Cache no Navegador

Utilize as Ferramentas do Desenvolvedor do navegador (geralmente F12):

  1. Abra a aba "Network".
  2. Selecione o recurso que deseja inspecionar.
  3. Verifique os "Response Headers" para Cache-Control, ETag, Expires, Last-Modified.
  4. Observe a coluna "Size":
    • (memory cache) ou (disk cache): Cache forte atingido, sem requisição de rede.
    • 304 Not Modified: Cache negociado atingido, validação bem-sucedida.
    • Tamanho do arquivo (ex: 1.5 kB): Requisição completa do servidor, cache não atingido ou expirado.
  5. Recarregue a página (Ctrl+R ou F5). Se o cache estiver configurado corretamente, muitos recursos estáticos mostrarão (disk cache) ou 304 Not Modified.
  6. Use o cache do navegador para testes: desmarque "Disable cache" nas ferramentas do desenvolvedor para ver o comportamento normal. Limpe o cache do navegador (Ctrl+Shift+Delete) para simular um primeiro acesso.

Configuração Completa para Produção (Exemplo)


http {
   # Definição de zona de cache para proxy reverso
   # proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=api_cache:10m max_size=1g inactive=60m use_temp_path=off;

   server {
       listen 80;
       server_name seu-dominio.com;
       root /var/www/html;

       # 1. Recursos Estáticos Versionados (Vite/Webpack) - Cache Permanente
       location ~* .*-[a-f0-9]{8}\.(js|css|mjs|ts|jsx|tsx|wasm)$ {
           expires max; # Define Expires para um tempo muito distante
           add_header Cache-Control "public, immutable, max-age=31536000"; # 1 ano
           add_header Vary "Accept-Encoding"; # Importante para caches que lidam com compressão
           access_log off;
           gzip_static on; # Se usar ficheiros .gz pré-compressos
       }

       # 2. Imagens e Mídia - Cache de Longo Prazo
       location ~* \.(jpg|jpeg|png|gif|ico|webp|svg|avif|mp4|webm)$ {
           expires 1y; # Cache por 1 ano
           add_header Cache-Control "public, max-age=31536000";
           add_header Vary "Accept-Encoding";
           access_log off;
       }

       # 3. Fontes - Cache de Longo Prazo
       location ~* \.(woff|woff2|ttf|eot|otf)$ {
           expires 1y;
           add_header Cache-Control "public, max-age=31536000";
           add_header Access-Control-Allow-Origin "*"; # CORS se necessário
           access_log off;
       }

       # 4. Arquivos HTML - Sem Cache
       location ~* \.html$ {
           add_header Cache-Control "no-cache, no-store, must-revalidate";
           add_header Pragma "no-cache";
           add_header Expires "0";
       }

       # 5. APIs (Exemplo com Proxy Reverso)
       # location /api/static/ {
       #     proxy_pass http://backend_server;
       #     proxy_cache api_cache; # Usa a zona de cache definida em http block
       #     proxy_cache_valid 200 302 10m; # Cache de 10 minutos para códigos 200 e 302
       #     proxy_cache_valid 404 1m;      # Cache de 1 minuto para 404
       #     add_header X-Cache-Status $upstream_cache_status;
       #     add_header Cache-Control "public, max-age=600"; # Cache de 10 minutos
       # }

       # location /api/dynamic/ {
       #     proxy_pass http://backend_server;
       #     add_header Cache-Control "private, max-age=60, must-revalidate"; # Cache privado de 1 minuto
       # }

       # 6. Service Worker - Sem Cache
       location /sw.js {
           add_header Cache-Control "no-cache, max-age=0";
       }

       # 7. Locação Padrão (Para SPA, etc.)
       location / {
           try_files $uri $uri/ /index.html;
           add_header Cache-Control "no-cache"; # Geralmente o HTML é servido sem cache
       }
   }
}
   

Tags: nginx cache-control HTTP caching performance

Publicado em 6-7 21:46 por Thomas