Compresión Gzip/Brotli en Apache y Nginx
Introducción
La compresión es una de las formas más efectivas y fáciles de mejorar el rendimiento de un sitio web. Al comprimir contenido basado en texto antes de la transmisión, los servidores web pueden reducir el uso de ancho de banda en un 60-90%, disminuyendo drámáticamente los tiempos de carga de página y mejorando la experiencia del usuario. Gzip ha sido el estándar de la industria durante décadas, mientras que Brotli es un algoritmo más nuevo que ofrece ratios de compresión 15-30% mejores.
Para sitios web modernos que sirven megabytes de HTML, CSS, JavaScript, JSON y otros formatos de texto, habilitar la compresión no es opcional, es esencial. Una página web sin comprimir típica podría ser de 2-3 MB, pero con compresión Brotli, puede reducirse a 400-600 KB, reduciendo el tiempo de carga de 6 segundos a menos de 1 segundo en una conexión 4G. Esta mejora impacta directamente en los rankings de SEO, tasas de conversión y satisfacción del usuario.
Esta guía completa cubre tanto la configuración de compresión Gzip como Brotli para Apache y Nginx, comparaciones de rendimiento, técnicas de optimización y solución de problemas. Aprenderás cómo implementar la compresión correctamente para lograr los máximos beneficios de rendimiento mientras evitas errores comunes.
Entendiendo la Compresión
Por Qué Importa la Compresión
Beneficios de Rendimiento:
- Ancho de Banda Reducido: 60-90% menos datos transferidos
- Tiempos de Carga Más Rápidos: Cargas de página 3-10x más rápidas
- Costos Más Bajos: Costos de ancho de banda reducidos
- Mejor SEO: Google favorece sitios rápidos
- UX Mejorada: Más rápido es siempre mejor
Gzip vs Brotli
| Característica | Gzip | Brotli |
|---|---|---|
| Ratio de Compresión | Bueno | Mejor (15-30% más) |
| Velocidad de Compresión | Rápida | Más lenta |
| Velocidad de Descompresión | Rápida | Rápida |
| Soporte de Navegadores | Universal (99.9%) | Moderno (95%+) |
| Uso de CPU | Bajo | Medio |
| Mejor Para | Todo contenido | Assets estáticos |
Efectividad de Compresión por Tipo de Archivo
| Tipo de Archivo | Ratio de Compresión | ¿Vale la Pena Comprimir? |
|---|---|---|
| HTML | 70-85% | Sí |
| CSS | 70-85% | Sí |
| JavaScript | 60-80% | Sí |
| JSON | 70-90% | Sí |
| XML | 75-85% | Sí |
| SVG | 60-80% | Sí |
| Texto Plano | 60-80% | Sí |
| Imágenes (JPEG/PNG) | 0-5% | No (ya comprimidas) |
| Video | 0-2% | No (ya comprimido) |
| 5-15% | Quizás |
Comparativa Sin Compresión
Probar Tamaño de Página
# Create test HTML file
cat > /var/www/html/compression-test.html << 'EOF'
<!DOCTYPE html>
<html>
<head>
<title>Compression Test</title>
<style>
body { font-family: Arial; padding: 20px; }
.content { line-height: 1.6; }
</style>
</head>
<body>
<h1>Compression Test Page</h1>
<div class="content">
<!-- Repeated content to create size -->
Lorem ipsum dolor sit amet... (repeated 100 times)
</div>
<script>
console.log('Page loaded');
// More JavaScript code...
</script>
</body>
</html>
EOF
# Check uncompressed size
curl -H "Accept-Encoding: identity" http://localhost/compression-test.html -o test.html
ls -lh test.html
# Size: 156KB uncompressed
Rendimiento Base
# Test without compression
curl -w "\nSize: %{size_download} bytes\nTime: %{time_total}s\n" \
-H "Accept-Encoding: identity" \
-o /dev/null http://localhost/compression-test.html
# Results (typical):
# Size: 156,842 bytes
# Time: 0.234s (on slow connection: 2.5s)
# Full page load test
curl -w "Time: %{time_total}s\n" http://localhost/ -o /dev/null
# Time: 3.8s (multiple resources)
Configuración Gzip
Configuración Gzip en Nginx
# /etc/nginx/nginx.conf or /etc/nginx/sites-available/default
http {
# Enable Gzip compression
gzip on;
# Compression level (1-9)
# 1 = fastest, least compression
# 9 = slowest, most compression
# 5-6 = good balance (recommended)
gzip_comp_level 6;
# Minimum file size to compress (bytes)
# Don't compress tiny files (overhead not worth it)
gzip_min_length 1000;
# Compress for all clients (even old browsers)
gzip_proxied any;
# File types to compress
gzip_types
text/plain
text/css
text/xml
text/javascript
application/json
application/javascript
application/xml+rss
application/rss+xml
application/atom+xml
image/svg+xml
text/x-component
text/x-cross-domain-policy;
# Enable compression for proxied requests
gzip_vary on;
# Disable for old IE6 (optional, IE6 is dead)
gzip_disable "msie6";
# Buffer size (default: 32 4k or 16 8k)
gzip_buffers 16 8k;
# HTTP version (1.1 recommended)
gzip_http_version 1.1;
# Rest of nginx configuration...
}
Configuración Gzip en Apache
# /etc/apache2/mods-available/deflate.conf
# Or in .htaccess
<IfModule mod_deflate.c>
# Enable compression
SetOutputFilter DEFLATE
# Compression level (1-9)
# 6 is good balance
DeflateCompressionLevel 6
# Don't compress images, videos, PDFs
SetEnvIfNoCase Request_URI \.(?:gif|jpe?g|png|ico|pdf|flv|swf|gz|zip|bz2|rar|7z)$ no-gzip
# File types to compress
AddOutputFilterByType DEFLATE text/plain
AddOutputFilterByType DEFLATE text/html
AddOutputFilterByType DEFLATE text/xml
AddOutputFilterByType DEFLATE text/css
AddOutputFilterByType DEFLATE text/javascript
AddOutputFilterByType DEFLATE application/xml
AddOutputFilterByType DEFLATE application/xhtml+xml
AddOutputFilterByType DEFLATE application/rss+xml
AddOutputFilterByType DEFLATE application/atom+xml
AddOutputFilterByType DEFLATE application/javascript
AddOutputFilterByType DEFLATE application/x-javascript
AddOutputFilterByType DEFLATE application/json
AddOutputFilterByType DEFLATE image/svg+xml
# Vary header (for proper caching)
Header append Vary Accept-Encoding
# Netscape 4.x issues
BrowserMatch ^Mozilla/4 gzip-only-text/html
# Netscape 4.06-4.08 issues
BrowserMatch ^Mozilla/4\.0[678] no-gzip
# MSIE masquerades as Netscape
BrowserMatch \bMSIE !no-gzip !gzip-only-text/html
# Don't compress if already compressed
SetEnvIfNoCase Request_URI \.(?:exe|t?gz|zip|bz2|sit|rar)$ no-gzip
# Proxy settings
Header append Vary User-Agent env=!dont-vary
# Memory level (1-9, 8 is default)
DeflateMemLevel 8
# Window size (9-15, 15 is default)
DeflateWindowSize 15
# Buffer size
DeflateBufferSize 8096
</IfModule>
Probar Compresión Gzip
# Test if Gzip is working
curl -H "Accept-Encoding: gzip" -I http://localhost/compression-test.html | grep -i "content-encoding"
# Should show: Content-Encoding: gzip
# Compare sizes
# Uncompressed
curl -H "Accept-Encoding: identity" http://localhost/test.html -o test-uncompressed.html
ls -lh test-uncompressed.html
# Size: 156KB
# Compressed
curl -H "Accept-Encoding: gzip" http://localhost/test.html -o test-compressed.html.gz
ls -lh test-compressed.html.gz
# Size: 24KB (85% reduction)
# Detailed test with size and time
curl -w "\nOriginal: %{size_download} bytes\nTime: %{time_total}s\n" \
-H "Accept-Encoding: gzip" \
-o /dev/null http://localhost/compression-test.html
# Results:
# Original: 24,156 bytes (compressed)
# Time: 0.042s (85% faster)
Configuración Brotli
Instalar Brotli
Nginx:
# Ubuntu/Debian - Install from package
apt-get install nginx-module-brotli -y
# Or compile from source
apt-get install git gcc make libpcre3-dev zlib1g-dev -y
git clone https://github.com/google/ngx_brotli.git
cd ngx_brotli
git submodule update --init
# Compile with Nginx
./configure --add-module=/path/to/ngx_brotli
make && make install
Apache:
# Ubuntu/Debian
apt-get install libbrotli-dev -y
# Install mod_brotli
git clone https://github.com/apache/httpd.git
cd httpd/modules/filters
git clone https://github.com/kjdev/apache-mod-brotli.git mod_brotli
cd mod_brotli
apxs -i -a -c mod_brotli.c -lbrotlienc
Configuración Brotli en Nginx
# /etc/nginx/nginx.conf
# Load Brotli module
load_module modules/ngx_http_brotli_filter_module.so;
load_module modules/ngx_http_brotli_static_module.so;
http {
# Enable Brotli compression
brotli on;
# Compression level (0-11)
# 4-6 = good balance (recommended)
# Higher = better compression but slower
brotli_comp_level 6;
# File types to compress
brotli_types
text/plain
text/css
text/xml
text/javascript
application/json
application/javascript
application/xml+rss
application/rss+xml
application/atom+xml
image/svg+xml
application/x-font-ttf
application/x-font-opentype
application/vnd.ms-fontobject
application/x-web-app-manifest+json;
# Minimum file size to compress
brotli_min_length 1000;
# Window size (default: 512k)
brotli_window 512k;
# Buffer size
brotli_buffers 16 8k;
# Enable for static files (pre-compressed .br files)
brotli_static on;
# Keep Gzip as fallback
gzip on;
gzip_comp_level 6;
gzip_types text/plain text/css text/xml text/javascript application/json application/javascript;
# Rest of configuration...
}
Configuración Brotli en Apache
# /etc/apache2/mods-available/brotli.conf
<IfModule mod_brotli.c>
# Enable Brotli
SetOutputFilter BROTLI
# Compression quality (0-11)
# 4-6 recommended for dynamic content
BrotliCompressionQuality 6
# Window size (10-24, 22 is good)
BrotliCompressionWindow 22
# File types to compress
AddOutputFilterByType BROTLI_COMPRESS text/html
AddOutputFilterByType BROTLI_COMPRESS text/plain
AddOutputFilterByType BROTLI_COMPRESS text/xml
AddOutputFilterByType BROTLI_COMPRESS text/css
AddOutputFilterByType BROTLI_COMPRESS text/javascript
AddOutputFilterByType BROTLI_COMPRESS application/xml
AddOutputFilterByType BROTLI_COMPRESS application/javascript
AddOutputFilterByType BROTLI_COMPRESS application/json
AddOutputFilterByType BROTLI_COMPRESS image/svg+xml
# Keep Gzip as fallback for older browsers
<IfModule mod_deflate.c>
SetOutputFilter BROTLI_COMPRESS;DEFLATE
</IfModule>
# Vary header
Header append Vary Accept-Encoding
</IfModule>
Probar Compresión Brotli
# Test if Brotli is working
curl -H "Accept-Encoding: br" -I http://localhost/test.html | grep -i "content-encoding"
# Should show: Content-Encoding: br
# Compare sizes: Uncompressed vs Gzip vs Brotli
# Uncompressed
curl -H "Accept-Encoding: identity" http://localhost/test.html | wc -c
# Size: 156,842 bytes
# Gzip
curl -H "Accept-Encoding: gzip" http://localhost/test.html | wc -c
# Size: 24,156 bytes (85% reduction)
# Brotli
curl -H "Accept-Encoding: br" http://localhost/test.html | wc -c
# Size: 20,987 bytes (87% reduction, 13% better than Gzip)
Pre-Compresión (Compresión Estática)
Pre-Comprimir Assets
Para assets estáticos, pre-comprimir durante el build:
#!/bin/bash
# compress-assets.sh
# Find all static assets
find /var/www/html/assets -type f \( -name "*.css" -o -name "*.js" -o -name "*.svg" -o -name "*.html" \) | while read file; do
# Create Gzip version
gzip -9 -k -f "$file"
# Create Brotli version (if brotli installed)
if command -v brotli &> /dev/null; then
brotli -9 -k -f "$file"
fi
echo "Compressed: $file"
done
Compresión Estática en Nginx
# Serve pre-compressed files
location ~* \.(css|js|svg|html)$ {
# Try .br file first (Brotli)
gzip_static on;
brotli_static on;
# Example request flow:
# Request: /style.css
# Try: /style.css.br (if client supports Brotli)
# Try: /style.css.gz (if client supports Gzip)
# Serve: /style.css (uncompressed fallback)
# Cache headers
expires 1y;
add_header Cache-Control "public, immutable";
}
Beneficios de la Pre-Compresión
Compresión Dinámica:
- Uso de CPU por solicitud: Alto
- Tiempo de compresión: 10-50ms por solicitud
- Mejor compresión: Buena (nivel 6)
Pre-Compresión:
- Uso de CPU por solicitud: Ninguno
- Tiempo de compresión: 0ms (ya comprimido)
- Mejor compresión: Excelente (nivel 11)
- Tiempo de build: Aumentado
- Espacio en disco: 2-3x (original + .gz + .br)
Optimización del Nivel de Compresión
Comparación de Niveles de Compresión
# Test different Gzip levels
for level in {1..9}; do
echo "Testing Gzip level $level"
time gzip -$level -k -c /var/www/html/large-file.js > /tmp/test-$level.js.gz
ls -lh /tmp/test-$level.js.gz
done
# Results (typical 500KB JS file):
# Level 1: 156KB, 0.02s (fast, 69% reduction)
# Level 4: 138KB, 0.06s (balanced, 72% reduction)
# Level 6: 134KB, 0.12s (recommended, 73% reduction)
# Level 9: 132KB, 0.35s (slow, 74% reduction)
# Recommendation: Level 6 for dynamic, Level 9 for pre-compression
Balance entre Rendimiento y Compresión
# Benchmark compression levels under load
ab -n 1000 -c 100 http://localhost/large-page.html
# Results:
# No compression: 1,250 req/s, 450ms response
# Gzip level 1: 3,180 req/s, 140ms response
# Gzip level 6: 2,850 req/s, 155ms response
# Gzip level 9: 2,120 req/s, 210ms response
# Brotli level 4: 2,940 req/s, 150ms response
# Brotli level 11 (pre): 3,620 req/s, 120ms response
# Best: Brotli level 11 pre-compressed
Ejemplos de Configuración
Sitio de Alto Tráfico (Nginx)
http {
# Brotli (primary)
brotli on;
brotli_comp_level 4; # Fast compression for dynamic content
brotli_types text/plain text/css text/javascript application/json application/javascript image/svg+xml;
brotli_min_length 1000;
brotli_static on; # Use pre-compressed .br files
# Gzip (fallback)
gzip on;
gzip_comp_level 5; # Fast compression
gzip_types text/plain text/css text/javascript application/json application/javascript image/svg+xml;
gzip_min_length 1000;
gzip_static on; # Use pre-compressed .gz files
gzip_vary on;
# Disable for IE6
gzip_disable "msie6";
# Cache settings
location ~* \.(css|js|svg)$ {
expires 1y;
add_header Cache-Control "public, immutable";
brotli_static on;
gzip_static on;
}
}
Servidor API (Nginx)
http {
# Optimize for JSON responses
brotli on;
brotli_comp_level 6; # Better compression for JSON
brotli_types application/json application/javascript;
brotli_min_length 256; # Compress even small JSON
gzip on;
gzip_comp_level 6;
gzip_types application/json application/javascript;
gzip_min_length 256;
gzip_vary on;
# No static compression needed (dynamic API)
brotli_static off;
gzip_static off;
}
Sitio WordPress (Apache)
<IfModule mod_brotli.c>
SetOutputFilter BROTLI_COMPRESS
BrotliCompressionQuality 4
AddOutputFilterByType BROTLI_COMPRESS text/html
AddOutputFilterByType BROTLI_COMPRESS text/css
AddOutputFilterByType BROTLI_COMPRESS text/javascript
AddOutputFilterByType BROTLI_COMPRESS application/javascript
AddOutputFilterByType BROTLI_COMPRESS application/json
Header append Vary Accept-Encoding
</IfModule>
<IfModule mod_deflate.c>
SetOutputFilter DEFLATE
DeflateCompressionLevel 5
AddOutputFilterByType DEFLATE text/html
AddOutputFilterByType DEFLATE text/css
AddOutputFilterByType DEFLATE text/javascript
AddOutputFilterByType DEFLATE application/javascript
AddOutputFilterByType DEFLATE application/json
Header append Vary Accept-Encoding
</IfModule>
# Cache static assets
<filesMatch "\.(css|js|svg)$">
Header set Cache-Control "max-age=31536000, public"
</filesMatch>
Pruebas de Rendimiento
Comparación Antes y Después
# Test suite
echo "=== Compression Performance Test ==="
# Test 1: Large HTML page
echo "Test 1: HTML Page (250KB)"
curl -w "Uncompressed: %{size_download} bytes, %{time_total}s\n" \
-H "Accept-Encoding: identity" -o /dev/null http://localhost/large.html
curl -w "Gzip: %{size_download} bytes, %{time_total}s\n" \
-H "Accept-Encoding: gzip" -o /dev/null http://localhost/large.html
curl -w "Brotli: %{size_download} bytes, %{time_total}s\n" \
-H "Accept-Encoding: br" -o /dev/null http://localhost/large.html
# Results:
# Uncompressed: 250,000 bytes, 0.342s
# Gzip: 38,500 bytes, 0.065s (85% smaller, 80% faster)
# Brotli: 32,750 bytes, 0.058s (87% smaller, 83% faster)
# Test 2: JavaScript bundle
echo "Test 2: JavaScript (1.2MB)"
curl -w "Uncompressed: %{size_download} bytes\n" \
-H "Accept-Encoding: identity" -o /dev/null http://localhost/app.js
curl -w "Gzip: %{size_download} bytes\n" \
-H "Accept-Encoding: gzip" -o /dev/null http://localhost/app.js
curl -w "Brotli: %{size_download} bytes\n" \
-H "Accept-Encoding: br" -o /dev/null http://localhost/app.js
# Results:
# Uncompressed: 1,200,000 bytes
# Gzip: 285,000 bytes (76% reduction)
# Brotli: 245,000 bytes (80% reduction)
# Test 3: Full page load
ab -n 100 -c 10 http://localhost/
# Without compression: 5.2s page load, 3.4MB transferred
# With compression: 1.1s page load, 620KB transferred (79% less data, 79% faster)
Monitoreo y Solución de Problemas
Verificar que la Compresión Funciona
#!/bin/bash
# check-compression.sh
URL="$1"
echo "Checking compression for: $URL"
echo
# Check Content-Encoding header
ENCODING=$(curl -s -I -H "Accept-Encoding: gzip, br" "$URL" | grep -i "content-encoding:")
echo "Content-Encoding: $ENCODING"
# Get sizes
UNCOMPRESSED=$(curl -s -H "Accept-Encoding: identity" "$URL" | wc -c)
GZIP=$(curl -s -H "Accept-Encoding: gzip" "$URL" | wc -c)
BROTLI=$(curl -s -H "Accept-Encoding: br" "$URL" | wc -c)
echo "Uncompressed: $UNCOMPRESSED bytes"
echo "Gzip: $GZIP bytes ($(awk "BEGIN {printf \"%.1f\", (1-$GZIP/$UNCOMPRESSED)*100}")% reduction)"
echo "Brotli: $BROTLI bytes ($(awk "BEGIN {printf \"%.1f\", (1-$BROTLI/$UNCOMPRESSED)*100}")% reduction)"
Problemas Comunes
Problema 1: La Compresión No Funciona
# Check module loaded (Nginx)
nginx -V 2>&1 | grep -o with-http_gzip_static_module
# Check module enabled (Apache)
apachectl -M | grep deflate
apachectl -M | grep brotli
# Check configuration syntax
nginx -t
apachectl configtest
Problema 2: Tipos de Contenido Incorrectos
# Verify Content-Type header
curl -I http://localhost/style.css | grep -i content-type
# Should be: Content-Type: text/css
# Fix in Nginx mime.types
types {
text/css css;
text/javascript js;
application/json json;
}
Problema 3: Contenido Ya Comprimido
# Don't compress images, videos
# In Nginx:
gzip_types text/plain text/css text/javascript; # Don't add image/*
# In Apache:
SetEnvIfNoCase Request_URI \.(?:gif|jpe?g|png|mp4)$ no-gzip
Mejores Prácticas
-
Siempre Habilitar Compresión
- Ganancia de rendimiento enorme, costo mínimo
- Usa Brotli con fallback a Gzip
-
Elegir el Nivel de Compresión Correcto
- Contenido dinámico: Nivel 4-6
- Pre-comprimido: Nivel 9-11
-
Comprimir los Tipos de Archivo Correctos
- Comprimir: HTML, CSS, JS, JSON, XML, SVG, fuentes
- No comprimir: Imágenes, videos, binarios
-
Establecer Tamaño Mínimo
- No comprimir archivos < 1KB
- El overhead no vale la pena
-
Usar Pre-Compresión
- Pre-comprimir assets estáticos durante build
- Máxima compresión, costo de runtime cero
-
Establecer Header Vary
- Esencial para cacheo correcto
- Header: Vary: Accept-Encoding
-
Monitorear Rendimiento
- Rastrear ratio de compresión
- Monitorear uso de CPU
- Medir tiempos de carga de página reales
Conclusión
La compresión es una de las optimizaciones de mayor impacto y menor esfuerzo disponibles:
Mejoras de Rendimiento:
- Uso de ancho de banda: 60-90% de reducción
- Tiempo de carga de página: 50-85% más rápido
- Experiencia del usuario: Dramáticamente mejor
- Ranking SEO: Mejorado
- Costos de infraestructura: 40-70% menores costos de ancho de banda
Resumen de Implementación:
- Gzip: Soporte universal, buena compresión, bajo CPU
- Brotli: Mejor compresión (15-30%), navegadores modernos
- Mejor enfoque: Ambos (Brotli con fallback a Gzip)
- Niveles óptimos: 4-6 dinámico, 9-11 pre-comprimido
- Qué comprimir: Archivos de texto (HTML, CSS, JS, JSON, XML, SVG)
- Qué no comprimir: Imágenes, videos, archivos ya comprimidos
Al implementar la configuración de compresión adecuada, puedes entregar sitios web dramáticamente más rápidos, reducir costos de infraestructura y proporcionar mejor experiencia de usuario con mínimo esfuerzo de configuración.


