gzip, Brotli, Zstd Compresión HTTP para desarrolladores que establecen content-encoding: identity por accidente
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.
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.
| Algoritmo | Nivel | Relación | Comprimir | Descomprimir |
|---|---|---|---|---|
| gzip | 1 (rápido) | 2.74x | 69 MB/s | 380 MB/s |
| gzip | 6 (predeterminado) | 2.97x | 29.9 MB/s | 360 MB/s |
| gzip | 9 (máximo) | 3.10x | 18 MB/s | 360 MB/s |
| Brotli | 4 | 3.18x | 104 MB/s | 440 MB/s |
| Brotli | 11 (máximo) | 3.74x | 0.4 MB/s | 440 MB/s |
| Zstd | 1 (rápido) | 2.88x | 430 MB/s | 1,380 MB/s |
| Zstd | 3 (predeterminado) | 3.01x | 320 MB/s | 1,350 MB/s |
| Zstd | 19 (máximo) | 3.40x | 17.5 MB/s | 1,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
| Algoritmo | Cromo | Firefox | Safari | Borde | Node.js |
|---|---|---|---|---|---|
| gzip | Todo | Todo | Todo | Todo | Incluido (zlib) |
| deflate | Todo | Todo | Todo | Todo | Incluido (zlib) |
| Brotli (br) | 51+ | 44+ | 11+ | 15+ | v10.16+ |
| Zstd | 118+ | 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:
- Probador de Gzip / Zlib / Deflate — pega tu payload, ve el tamaño comprimido y la relación inmediatamente
- Codificador/Decodificador de Compresión Brotli — prueba la compresión Brotli en diferentes niveles de calidad
- Herramienta de Compresión Zstandard (Zstd) — codifica y decodifica Zstd en el navegador
Ú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.
Instalar extensiones
Agregue herramientas IO a su navegador favorito para obtener acceso instantáneo y búsquedas más rápidas
恵 ¡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!
Herramientas clave
Ver todo Los recién llegados
Ver todoActualizar: Nuestro última herramienta fue agregado el 16 de junio de 2026
