Anúncios incomodam? Ir Sem anúncios Hoje

gzip, Brotli, Zstd Compressão HTTP para desenvolvedores que definem content-encoding: identity por acidente

Atualizado em

Como funciona a negociação de compressão HTTP (Accept-Encoding / Content-Encoding), uma comparação lado a lado entre gzip, Brotli e Zstd, como verificar se a compressão realmente está ativa com curl, e quatro configurações incorretas que silenciosamente desativam a compressão.

gzip, Brotli, Zstd: Compressão HTTP para desenvolvedores que definem content-encoding: identity por acidente 1
ANUNCIADO Remover?

Seu arquivo nginx tem gzip on;. Seu aplicativo retorna JSON. O corpo da resposta ainda está descomprimido em 35KB. Sem erros, sem avisos — a compressão simplesmente não está acontecendo.

Isso geralmente é uma das quatro configurações incorretas. Mas primeiro: como a negociação realmente funciona.

Como a negociação de compressão HTTP funciona

Dois cabeçalhos, nada mais. O cliente anuncia o que pode descomprimir em Accept-Encoding. O servidor escolhe um algoritmo, compreende o corpo e declara sua escolha em Content-Encoding:

GET /api/data HTTP/1.1
Host: example.com
Accept-Encoding: gzip, deflate, br, zstd
HTTP/1.1 200 OK
Content-Type: application/json
Content-Encoding: br
Vary: Accept-Encoding

O Vary: Accept-Encoding cabeçalho é opcional se você se importa pela correção do cache no CDN. Sem ele, um CDN pode armazenar uma resposta comprimida com Brotli e servir para um cliente que apenas anunciou gzip em Accept-Encoding. Esse cliente então tenta descomprimir Brotli como gzip e obtém lixo. O nginx gzip_vary on; adiciona isso automaticamente.

Content-Encoding: identity é tecnicamente válido — significa "nenhuma codificação" — mas ninguém o define explicitamente. O modo de falha real é o oposto: ausência de Content-Encoding cabeçalho completamente quando você esperava um.

Verificar se a compressão realmente está funcionando

Antes de depurar a configuração, confirme o problema:

# Check headers only
curl -sI -H "Accept-Encoding: gzip, br, zstd" https://example.com/api/data   | grep -i "content-encoding\|vary"

# Compare compressed vs uncompressed size
curl -so /dev/null -w "uncompressed: %{size_download} bytes
" https://example.com/api/data
curl -so /dev/null --compressed -w "compressed:   %{size_download} bytes
" https://example.com/api/data

--compressed envia Accept-Encoding: deflate, gzip, br, zstd automaticamente e descomprime a resposta. Se os dois números coincidirem, a compressão não está sendo ativada. Se você quiser inspecionar todos os cabeçalhos de resposta e o que cada um significa no contexto, o Analisador de Cabeçalhos HTTP anotará incluindo Vary, Content-Encodinge diretivas cache-control.

gzip vs Brotli vs Zstd

Três algoritmos são praticamente relevantes para HTTP hoje. Os números de benchmark abaixo provêm dos benchmarks oficiais do Zstd no corpus de Silesia — um conjunto padrão de arquivos do mundo real (HTML, código-fonte, PDFs, bancos de dados), testado em um processador Core i7-9700K. Cargas puras de JSON ou texto simples geralmente se comprimem melhor.

AlgoritmoNívelRelaçãoCompressaDescomprimir
gzip1 (rápido)2.74x69 MB/s380 MB/s
gzip6 (padrão)2.97x29.9 MB/s360 MB/s
gzip9 (máximo)3.10x18 MB/s360 MB/s
Brotli43.18x104 MB/s440 MB/s
Brotli11 (máximo)3.74x0.4 MB/s440 MB/s
Zstd1 (rápido)2.88x430 MB/s1,380 MB/s
Zstd3 (padrão)3.01x320 MB/s1,350 MB/s
Zstd19 (máximo)3.40x17.5 MB/s1,380 MB/s

gzip é a base. O nível 6 é a escolha adequada para o momento — gastar 65% mais de CPU para ir do nível 6 ao nível 9 ganha cerca de 4% de melhor relação. Não vale a pena para respostas dinâmicas. Arquivos estáticos pré-comprimidos são uma outra avaliação.

Brotli realmente supera o gzip em custo de CPU comparável nos níveis 4-6 e descompõe cerca de 20% mais rápido. A razão: o Brotli tem um dicionário estático ajustado para conteúdo web — entidades HTML, nomes de campos HTTP, palavras-chave JavaScript. Ele obtém melhores relações do que um compressor geral em materiais iguais. O nível 11 é apenas viável para ativos estáticos pré-comprimidos; com velocidade de compressão de 0,4 MB/s, você comprime cerca de 25 MB por minuto. Isso é um passo de build, não um manipulador de requisição.

Zstd é a história de velocidade. O nível padrão (3) tem a mesma relação que o gzip, mas compreende 10 vezes mais rápido e descompõe quase 4 vezes mais rápido. A principal limitação é o suporte nos navegadores: Chrome 118+ (outubro de 2023), Firefox 126+ (maio de 2024), Safari 18+ (final de 2024). Ainda não é universal o suficiente para ser usada como único algoritmo, mas se seu servidor negociar corretamente, adicionar Zstd custa apenas algumas linhas de configuração e ajuda clientes que o anunciam. O Zstd no nível 19 se aproxima do Brotli-11 em relação sem a penalidade catastrófica de velocidade de compressão, tornando-o mais útil para trabalhos em tempo real sob demandas altas de compressão.

Suporte de navegadores e clientes

AlgoritmoCromoRaposa de fogoSafáriBordaNode.js
gzipTodosTodosTodosTodosIntegrado (zlib)
deflateTodosTodosTodosTodosIntegrado (zlib)
Brotli (br)51+44+11+15+v10.16+
Zstd118+126+18+118+v21+

Uma característica importante: br e zstd aparecem apenas em Accept-Encoding em conexões HTTPS. Os navegadores não anunciam intencionalmente esses cabeçalhos em HTTP puro — é uma defesa contra ataques MITM que poderiam injetar cabeçalhos de codificação. Se você estiver testando em http://localhost e estiver se perguntando por que você só vê gzip, deflate, é por isso. Teste via HTTPS ou use o curl diretamente (o curl não aplica essa restrição).

Quatro configurações que quebram silenciosamente

1. gzip_proxied está faltando (nginx reverse proxy)

nginx’s gzip módulo compreende respostas que ele gera. Para requisições proxy (aplicativo upstream para nginx para cliente), você precisa gzip_proxied — caso contrário, o nginx apenas compreende respostas de seus próprios manipuladores de conteúdo, não de proxy_pass upstreams.

# This is NOT enough when nginx is a reverse proxy:
gzip on;
gzip_types text/plain application/json application/javascript text/css;

# You need this too:
gzip_proxied any;

A maioria das configurações nginx são reverse proxies. A maioria dos tutoriais omite gzip_proxied. Essas duas verdades explicam muitos casos de respostas não comprimidas.

2. Tipo MIME não está em gzip_types

nginx’s default gzip_types é text/html apenas. JSON, CSS, JavaScript, SVG — todos não comprimidos a menos que listados explicitamente:

gzip_types
  text/plain
  text/css
  text/xml
  text/javascript
  application/json
  application/javascript
  application/xml
  application/rss+xml
  image/svg+xml;

nginx verifica pelo tipo MIME base, então application/json abrange application/json; charset=utf-8. Não é necessário listar variantes de charset separadamente.

3. Um proxy intermediário remove Accept-Encoding

AWS ALB, workers mal configurados do Cloudflare e algumas configurações de API gateway removem ou reescrevem Accept-Encoding antes que cheguem ao seu origem. O servidor nunca vê o cabeçalho, assume ausência de compressão e todos os downstream pensam que a funcionalidade está quebrada quando o problema real é o middleware. Nenhum erro aparece em qualquer parte da cadeia.

Depure comparando a resposta do origem com a resposta do CDN:

# Via CDN/proxy
curl -sI -H "Accept-Encoding: gzip, br" https://example.com/api/data

# Direct to origin (bypassing CDN via --resolve or direct IP)
curl -sI -H "Accept-Encoding: gzip, br" --resolve "example.com:443:ORIGIN_IP" https://example.com/api/data

Se o origem retorna Content-Encoding: gzip diretamente mas a resposta do CDN não tem Content-Encoding, o CDN está removendo algo — mais provavelmente removendo Accept-Encoding no ingresso para que o origem nunca compreenda no início.

4. O aplicativo upstream compreende, então o nginx tenta compreender novamente

Se seu aplicativo Node.js/Go/Python compreende o corpo da resposta e define Content-Encoding: gzip, o nginx deveria pular a compressão dupla — mas isso depende do timing do cabeçalho. Se o upstream envia o cabeçalho no meio do fluxo ou se a detecção do nginx se rivaliza, você pode acabar com lixo comprimido duplicado que os clientes não conseguem decodificar.

A solução limpa: deixe o nginx controlar toda a compressão. Remova o middleware de compressão do seu aplicativo (módulo compression do express, módulo gzip.Handlerdo Go, etc.), retorne respostas brutas e deixe o nginx comprimir na borda. Mesma melhora de desempenho, sem risco de compressão dupla.

Configurações funcionais

nginx

gzip on;
gzip_vary on;         # adds Vary: Accept-Encoding automatically
gzip_proxied any;     # compress responses from proxied upstreams
gzip_comp_level 6;
gzip_min_length 256;  # skip tiny responses where overhead isn't worth it
gzip_types
  text/plain
  text/css
  text/xml
  text/javascript
  application/json
  application/javascript
  application/xml
  application/rss+xml
  image/svg+xml;

# Brotli requires the ngx_brotli module
# https://github.com/google/ngx_brotli
brotli on;
brotli_comp_level 4;
brotli_static on;     # serve pre-compressed .br files when they exist
brotli_types
  text/plain
  text/css
  application/json
  application/javascript
  image/svg+xml;

Apache

LoadModule deflate_module modules/mod_deflate.so
AddOutputFilterByType DEFLATE text/html text/plain text/css   application/json application/javascript image/svg+xml
Header append Vary Accept-Encoding

# mod_brotli requires Apache 2.4.26+
LoadModule brotli_module modules/mod_brotli.so
AddOutputFilterByType BROTLI_COMPRESS text/html text/plain text/css   application/json application/javascript image/svg+xml

Caddy

Caddy habilita gzip e Brotli por padrão. Para adicionar Zstd explicitamente:

example.com {
  encode gzip zstd br
  reverse_proxy localhost:3000
}

Sem listas de tipos MIME, sem gzip_proxied casos extremos, tratamento correto Vary de saída por padrão. A resposta honesta à pergunta "qual servidor tem a menor superfície de configuração relacionada à compressão" é Caddy.

Testando compressão em seus próprios payloads

Os números de benchmark no corpus de Silesia dizem sobre desempenho relativo, mas seu payload específico importa mais. Uma resposta JSON repetitiva com nomes de campo consistentes se compreende de forma diferente do JavaScript minificado ou HTML misto. Essas ferramentas permitem testar payloads específicos no navegador sem precisar rodar um servidor local de compressão:

Útil para decidir se é melhor pré-comprimir ativos estáticos em Brotli-11 ou deixar o nginx lidar com gzip em tempo real. Cole seu payload real, compare as relações e faça a escolha com números reais.

Concluindo

Se as respostas não são comprimidas e curl -sI confirma a ausência de Content-Encoding, a correção é quase certamente uma das quatro configurações acima — mais provavelmente gzip_proxied any; no nginx, ou um CDN que está comendo seu Accept-Encoding cabeçalho. Verifique diretamente o origem antes de culpar sua configuração do servidor.

Para a escolha do algoritmo: gzip-6 é suficiente para respostas dinâmicas de API e carrega quase zero risco de configuração. Adicione Brotli para ativos estáticos — pré-comprime no nível 11 durante seu passo de build, sirva com brotli_static one deixe o nginx cair de volta para gzip para clientes que não anunciaram br. O Zstd vale a pena adicionar agora; o custo de configuração é trivial e seu footprint nos navegadores está crescendo rapidamente. Oferecer os três com tratamento correto Vary: Accept-Encoding é a postura correta para qualquer novo projeto.

Quer eliminar anúncios? Fique sem anúncios hoje mesmo

Instale nossas extensões

Adicione ferramentas de IO ao seu navegador favorito para acesso instantâneo e pesquisa mais rápida

Ao Extensão do Chrome Ao Extensão de Borda Ao Extensão Firefox Ao Extensão Opera

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!

ANUNCIADO Remover?
ANUNCIADO Remover?
ANUNCIADO Remover?

Notícias com destaques técnicos

Envolver-se

Ajude-nos a continuar fornecendo ferramentas gratuitas valiosas

Compre-me um café
ANUNCIADO Remover?