¿Odias los anuncios? Ir Sin publicidad Hoy

gzip, Brotli, Zstd Compresión HTTP para desarrolladores que establecen content-encoding: identity por accidente

Actualizado en

Cómo funciona la negociación de compresión HTTP (Accept-Encoding / Content-Encoding), un benchmark lado a lado de gzip, Brotli y Zstd, cómo verificar que la compresión está activa con curl, y cuatro configuraciones incorrectas que desactivan silenciosamente la compresión.

gzip, Brot 1, Zstd: Compresión HTTP para desarrolladores que establecen content-encoding: identity por accidente 1
ANUNCIO · ¿ELIMINAR?

Su configuración de nginx tiene gzip on;. Su aplicación devuelve JSON. El cuerpo de la respuesta aún mide 35 KB sin compresión. No hay errores, ni advertencias — la compresión simplemente no está ocurriendo silenciosamente.

Esto suele ser una de cuatro malas configuraciones. Pero primero: cómo funciona realmente la negociación.

Cómo funciona la negociación de compresión HTTP

Dos encabezados, nada más. El cliente anuncia qué puede descomprimir en Accept-Encoding. El servidor elige un algoritmo, compresiona el cuerpo y declara su elección en 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

El Vary: Accept-Encoding encabezado es obligatorio si te importa la corrección del almacenamiento en CDN. Sin él, un CDN podría almacenar una respuesta comprimida con Brotli y servirla a un cliente que solo anunció gzip en Accept-Encoding. Ese cliente entonces intenta descomprimir Brotli como gzip y obtiene basura. nginx’s gzip_vary on; agrega esto automáticamente.

Content-Encoding: identity es técnicamente válido — significa "sin codificación" — pero nadie lo establece explícitamente. El modo de fallo real es el opuesto: no Content-Encoding encabezado en absoluto cuando se esperaba uno.

Verificar que la compresión funcione realmente

Antes de depurar la configuración, confirma el 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 envía Accept-Encoding: deflate, gzip, br, zstd automáticamente y descomprime la respuesta. Si ambos números coinciden, la compresión no está activa. Si deseas inspeccionar todos los encabezados de respuesta y lo que cada uno significa en contexto, el Analizador de Encabezados HTTP anotará incluyendo Vary, Content-Encodingy directivas cache-control.

gzip vs Brotli vs Zstd

Tres algoritmos son prácticamente relevantes para HTTP hoy. Los números de referencia a continuación provienen de las pruebas oficiales de Zstd en el corpus de Silesia — un conjunto estándar de archivos reales del mundo (HTML, código fuente, PDFs, bases de datos), probado en un procesador Core i7-9700K. Cargas puras de JSON o texto plano suelen comprimirse mejor.

AlgoritmoNivelRelaciónComprimirDescomprimir
gzip1 (rápido)2.74x69 MB/s380 MB/s
gzip6 (predeterminado)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 (predeterminado)3.01x320 MB/s1,350 MB/s
Zstd19 (máximo)3.40x17.5 MB/s1,380 MB/s

gzip es la base. El nivel 6 es la elección adecuada en tiempo real — invertir 65% más de CPU para pasar del nivel 6 al 9 te aporta aproximadamente 4% de mejor relación. No vale la pena para respuestas dinámicas. Los archivos estáticos pre-comprimidos son una cálculo diferente.

Brotli realmente supera a gzip en costos de CPU comparables en niveles 4-6, y se descompresiona aproximadamente 20% más rápido. La razón: Brotli tiene un diccionario estático ajustado para contenido web — entidades HTML, nombres de campos HTTP, palabras clave de JavaScript. Logra mejores relaciones que un compresor general en el mismo material. El nivel 11 solo es viable para activos estáticos pre-comprimidos; a una velocidad de compresión de 0.4 MB/s, se comprime aproximadamente 25 MB por minuto. Eso es un paso de compilación, no un manejador de solicitud.

Zstd es la historia de velocidad. El nivel predeterminado (3) coincide con la relación de gzip pero compresiona 10 veces más rápido y se descompresiona casi 4 veces más rápido. La limitación principal es el soporte de navegadores: Chrome 118+ (octubre de 2023), Firefox 126+ (mayo de 2024), Safari 18+ (final de 2024). Aún no es universal para usarse como único algoritmo, pero si tu servidor negocia correctamente, agregar Zstd te cuesta solo unas líneas de configuración y ayuda a los clientes que lo anuncian. Zstd en nivel 19 se acerca a Brotli-11 en relación sin el costo catastrófico de velocidad de compresión, haciendo que sea más usable para trabajos en tiempo real bajo altas demandas de compresión.

Soporte de navegadores y clientes

AlgoritmoCromoFirefoxSafariBordeNode.js
gzipTodoTodoTodoTodoIncluido (zlib)
deflateTodoTodoTodoTodoIncluido (zlib)
Brotli (br)51+44+11+15+v10.16+
Zstd118+126+18+118+v21+

Una característica importante: br y zstd solo aparecen en Accept-Encoding sobre conexiones HTTPS. Los navegadores no anuncian intencionalmente estos encabezados sobre HTTP plano — es una defensa contra ataques MITM que podrían inyectar encabezados de codificación. Si estás probando en http://localhost y preguntas por qué solo ves gzip, deflate, eso es por qué. Prueba mediante HTTPS o usa curl directamente (curl no aplica esta restricción).

Cuatro malas configuraciones que rompen silenciosamente esta funcionalidad

1. gzip_proxied está faltando (proxy de nginx)

nginx’s gzip módulo compresiona las respuestas que genera por sí mismo. Para solicitudes proxy (aplicación upstream a nginx a cliente), necesitas gzip_proxied — de lo contrario, nginx solo compresiona respuestas de sus propios manejadores de contenido, no 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;

La mayoría de las configuraciones de nginx son proxies inversos. La mayoría de los tutoriales omite gzip_proxied. Estos dos hechos explican muchas respuestas no comprimidas silenciosamente.

2. El tipo MIME no está en gzip_types

nginx’s default gzip_types es text/html solo. JSON, CSS, JavaScript, SVG — todos sin compresión a menos que se liste explícitamente:

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

nginx coincide en el tipo MIME base, así que application/json cubre application/json; charset=utf-8. No es necesario listar variantes de charset por separado.

3. Un proxy intermedio elimina Accept-Encoding

AWS ALB, trabajadores mal configurados de Cloudflare, y algunos entornos de API gateway eliminan o modifican Accept-Encoding antes de que llegue a tu origen. El servidor nunca ve el encabezado, se queda sin compresión y todos los componentes posteriores piensan que la funcionalidad está rota cuando el problema real es el middleware. No aparece ningún error en la cadena.

Depuración comparando la respuesta del origen con la del 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

Si el origen devuelve Content-Encoding: gzip directamente pero la respuesta del CDN no tiene Content-Encoding, el CDN está eliminando algo — más probablemente eliminando Accept-Encoding entrante para que el origen nunca comprese en primer lugar.

4. La aplicación upstream compresiona, luego nginx intenta comprimir de nuevo

Si tu aplicación Node.js/Go/Python compresiona el cuerpo de la respuesta y establece Content-Encoding: gzip, nginx debería omitir la doble compresión — pero esto depende del timing del encabezado. Si la aplicación envía el encabezado en medio del flujo o si la detección de nginx compite, puedes terminar con basura doblemente comprimida que los clientes no pueden decodificar.

La solución limpia: deja que nginx controle toda la compresión. Elimina el middleware de compresión de tu aplicación (módulo compression de express, módulo gzip.Handlerde Go, etc.), devuelve respuestas sin compresión y deja que nginx las comprima en el borde. Mismos beneficios de rendimiento, sin riesgo de doble compresión.

Configuraciones funcionales

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 y Brotli por defecto. Para agregar Zstd explícitamente:

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

Sin listas de tipos MIME, sin gzip_proxied casos extremos, manejo correcto Vary por defecto. La respuesta honesta a la pregunta "cuál servidor tiene la menor superficie de malas configuraciones relacionadas con compresión" es Caddy.

Pruebas de compresión en tus propios payloads

Los números de referencia en el corpus de Silesia te indican el rendimiento relativo, pero tu payload específico importa más. Una respuesta JSON repetitiva con nombres de campos constantes se comprime de forma diferente a JavaScript minimizado o HTML mezclado. Estas herramientas te permiten probar payloads específicos en el navegador sin necesidad de arrancar un servidor local de compresión:

Útil para decidir si pre-comprimir activos estáticos en Brotli-11 o simplemente dejar que nginx maneje gzip en tiempo real. Pega tu respuesta real, compara las relaciones y toma la decisión con números reales.

Conclusión

Si las respuestas no están comprimidas y curl -sI confirma que no hay Content-Encoding, la solución es casi seguramente una de las cuatro malas configuraciones anteriores — más probablemente gzip_proxied any; para nginx, o un CDN que está comiendo tu Accept-Encoding encabezado. Verifica el origen directamente antes de culpar a tu configuración del servidor.

Para la elección del algoritmo: gzip-6 es adecuado para respuestas dinámicas de API y tiene casi ningún riesgo de configuración. Añade Brotli para activos estáticos — pre-comprime en nivel 11 durante tu paso de compilación, sirve con brotli_static on, y deja que nginx caiga de vuelta a gzip para clientes que no anuncian br. Zstd vale la pena añadir ahora; el costo de configuración es trivial y su footprint en navegadores crece rápidamente. Ofrecer los tres algoritmos con correcto Vary: Accept-Encoding manejo es la postura adecuada para cualquier nuevo sistema.

¿Quieres eliminar publicidad? Adiós publicidad hoy

Instalar extensiones

Agregue herramientas IO a su navegador favorito para obtener acceso instantáneo y búsquedas más rápidas

añadir Extensión de Chrome añadir Extensión de borde añadir Extensión de Firefox añadir Extensión de Opera

¡El marcador ha llegado!

Marcador es una forma divertida de llevar un registro de tus juegos, todos los datos se almacenan en tu navegador. ¡Próximamente habrá más funciones!

ANUNCIO · ¿ELIMINAR?
ANUNCIO · ¿ELIMINAR?
ANUNCIO · ¿ELIMINAR?

Noticias Aspectos técnicos clave

Involucrarse

Ayúdanos a seguir brindando valiosas herramientas gratuitas

Invítame a un café
ANUNCIO · ¿ELIMINAR?