Ajuste de rendimiento en Nginx: gzip, caché y pool de conexiones

La semana pasada saltó una alerta: la portada de un sitio de e-commerce tardaba 4 segundos en cargar. Abrí Chrome DevTools y vi un HTML de 120 KB y CSS más JS sumando 350 KB — todo sin comprimir. Peor aún: cada petición llegaba al backend y la tasa de aciertos en caché era solo del 12 %. Esa noche, tras quedarme hasta tarde, dediqué 2 horas a ajustar Nginx: activé gzip, completé la estrategia de caché y retocé el pool de conexiones. A la mañana siguiente, la portada cargaba en 1,6 segundos y el QPS del backend había bajado casi a la mitad.
Este tipo de problema es muy habitual. Mucha gente instala Nginx y lo deja correr: gzip desactivado por defecto, caché escrita a medias y límite de conexiones en valores por defecto. En picos de tráfico, el servidor se queda sin aliento.
Este artículo recoge la configuración de ajuste de rendimiento en Nginx que he validado en producción. La compresión gzip puede reducir el volumen de transferencia un 60-80 %; con 95 % de aciertos en caché la presión sobre el backend baja un 90 %; y un pool de conexiones bien configurado puede multiplicar la concurrencia por tres o cuatro. Repasaré cada módulo, los errores que he cometido y los datos medidos.
Capítulo 1: configuración gzip — reducir el volumen de transferencia
Primero, por qué gzip importa tanto. Imagina un HTML de 100 KB: comprimido con gzip puede quedar en 20-25 KB. Esos 75-80 KB ahorrados significan carga más rápida para el usuario y menor coste de tráfico para ti.
Me di cuenta en un proyecto de un cliente. Más del 60 % de usuarios eran móviles, muchos en 4G. La portada tardaba 3-4 segundos y la tasa de rebote superaba el 70 %. Tras activar gzip, el volumen de transferencia cayó un 70 % y el tiempo de primera pantalla bajó a unos 1,5 segundos.
1.1 Configuración básica: que funcione primero
La configuración gzip de Nginx no es compleja; el núcleo son unas pocas líneas:
http {
gzip on;
gzip_vary on;
gzip_min_length 1000;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml;
}
gzip on es el interruptor. gzip_vary on es clave: añade Vary: Accept-Encoding en la respuesta para que CDN y navegador sepan que el contenido varía según la capacidad de compresión del cliente, evitando errores de caché.
gzip_min_length en 1000 bytes significa no comprimir archivos menores de 1 KB. En archivos muy pequeños el beneficio es escaso y se gasta CPU. gzip_types define los MIME a comprimir; por defecto solo text/html — hay que añadir CSS, JS, JSON, XML, etc.
1.2 Configuración avanzada: nivel de compresión y lista de MIME
El nivel de compresión es un equilibrio. gzip_comp_level va de 1 a 9: a mayor número, más compresión y más CPU.
Hice una batería de pruebas:
| Nivel | Tasa HTML | Tiempo CPU (ms) | Escenario recomendado |
|---|---|---|---|
| 1 | 65% | 2 | CPU limitada |
| 4 | 72% | 3 | Equilibrado (recomendado) |
| 6 | 75% | 5 | Ancho de banda limitado (recomendado) |
| 9 | 78% | 12 | Escenario extremo |
Los niveles 4 y 6 son la mejor opción en la mayoría de casos. El nivel 9 duplica el consumo de CPU por unos pocos puntos más de compresión; no compensa.
Configuración gzip completa que uso en producción:
# Configuración de compresión gzip
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_min_length 1000;
gzip_types
text/plain
text/css
text/xml
text/javascript
application/json
application/javascript
application/xml
application/xml+rss
application/xhtml+xml
application/x-javascript;
gzip_disable "msie6";
gzip_proxied any se pasa por alto con facilidad. Si Nginx actúa como proxy inverso y la respuesta del backend no trae Content-Length, por defecto no comprime. Con any se fuerza la compresión de todas las respuestas que cumplan las condiciones.
gzip_disable "msie6" mantiene compatibilidad con IE6 antiguo, que tenía problemas con gzip. Hoy IE6 casi no existe; la línea se puede quitar, pero la dejo por precaución.
1.3 ¿Qué tipos de archivo rinden más?
Según mis mediciones, el efecto varía bastante por tipo:
| Tipo | Tamaño original | Comprimido | Tasa |
|---|---|---|---|
| HTML | 100KB | 20-25KB | 75-80% |
| CSS | 80KB | 24-28KB | 65-70% |
| JavaScript | 120KB | 36-42KB | 65-70% |
| API JSON | 50KB | 20-25KB | 50-60% |
| Imagen/vídeo | Ya comprimido | Sin sentido | 0-5% |
Imágenes y vídeos ya vienen comprimidos (JPEG, PNG, MP4); volver a gzip puede aumentar el tamaño. No añadas image/* ni video/* en gzip_types — sería contraproducente.
Una vez ayudé a diagnosticar un caso donde habían puesto image/jpeg en gzip_types y las imágenes crecían un 3-5 %. Error básico que yo también cometí al principio, cuando quería meter todos los MIME posibles.
Capítulo 2: estrategia de caché — acelerar contenido estático
La caché es el tramo con mayor retorno en optimización de rendimiento. Bien configurada, el 95 % de peticiones puede responderse desde Nginx sin tocar el backend. He visto sistemas con el backend al límite mientras la caché de Nginx casi no servía — no por no estar activa, sino por estar mal configurada.
2.1 ¿proxy_cache o fastcgi_cache?
Nginx ofrece dos mecanismos:
- proxy_cache: cachea respuestas del servidor upstream; para proxy inverso (Node.js, Python, Go, etc.)
- fastcgi_cache: cachea respuestas FastCGI; para PHP-FPM
Elige según el stack. PHP → fastcgi_cache; Node.js, Python, Go → proxy_cache. La lógica es casi la misma; el ejemplo siguiente usa proxy_cache.
2.2 Configuración completa de proxy_cache
Primero define la ruta de caché en el bloque http:
http {
proxy_cache_path /var/cache/nginx
levels=1:2
keys_zone=my_cache:10m
max_size=10g
inactive=60m
use_temp_path=off;
}
Línea por línea:
levels=1:2: niveles de directorio; 1:2 evita demasiados archivos en un solo directoriokeys_zone=my_cache:10m: nombre de zona y memoria para metadatos; 10m almacena unas 80 000 clavesmax_size=10g: tamaño máximo total; al superarlo se expulsa por LRUinactive=60m: entradas sin acceso en 60 minutos se eliminanuse_temp_path=off: escribe directo en el directorio de caché, sin coste de mover temporales
Luego actívalo en server o location:
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://backend;
proxy_cache my_cache;
# Validez de caché
proxy_cache_valid 200 302 10m;
proxy_cache_valid 404 1m;
proxy_cache_valid any 1m;
# Diseño de clave
proxy_cache_key $scheme$request_method$host$request_uri;
# Estrategia de degradación (más adelante)
proxy_cache_use_stale error timeout http_500 http_502 http_503 http_504;
# Cabecera de estado (depuración)
add_header X-Cache-Status $upstream_cache_status;
}
}
proxy_cache_valid define la duración por código de estado:
200 302 10m: respuestas correctas, 10 minutos404 1m: errores 404, 1 minuto — evita que peticiones maliciosas martillen el backendany 1m: otros códigos, 1 minuto
2.3 Diseño de clave e invalidación
proxy_cache_key define qué peticiones se consideran «iguales». Por defecto es $scheme$proxy_host$request_uri; yo recomiendo declararlo explícitamente:
proxy_cache_key $scheme$request_method$host$request_uri;
La clave incluye protocolo, método, host y URI completa. Con GET y POST o varios dominios, es más preciso.
Invalidar caché es incómodo. Estrategias habituales:
- Caducidad temporal:
proxy_cache_valid - Bypass activo:
proxy_cache_bypass - Purga: Nginx Plus comercial tiene
proxy_cache_purge
Suelo usar la segunda, controlada por cabecera:
# Cabecera específica para saltar caché
proxy_cache_bypass $http_x_nocache;
# O parámetro en la URL
proxy_cache_bypass $arg_nocache;
Para refrescar, añade ?nocache=1 o la cabecera X-Nocache: 1.
2.4 Degradación: servir aunque el backend falle
proxy_cache_use_stale es muy útil. Si el backend falla o hace timeout, Nginx puede devolver caché caducada en lugar de error directo.
proxy_cache_use_stale error timeout http_500 http_502 http_503 http_504;
El año pasado en el 11.11 (Singles’ Day), al escalar el backend hubo problemas y la API devolvía 502 intermitentes. Gracias a esta degradación, los usuarios apenas lo notaron — la caché estaba unos minutos vieja pero el contenido seguía visible. Al recuperarse el backend, la caché se actualizó sola.
Comparación de datos medidos:
| Métrica | Sin caché | Acierto | Modo degradación |
|---|---|---|---|
| Tiempo de respuesta | 150-200ms | 5-10ms | 5-10ms |
| QPS backend | 1000 | 50 | 0 |
| Experiencia | Normal | Normal | Algo más lenta |
Con acierto en caché, el tiempo baja de 200 ms a 5-10 ms — casi 20 veces más rápido. El beneficio es muy visible.
Capítulo 3: pool de conexiones — imprescindible en alta concurrencia
gzip y caché responden a «cómo transferir más rápido»; el pool de conexiones a «cómo soportar más peticiones». Con valores por defecto, un worker de Nginx solo admite 1024 conexiones concurrentes. Con tráfico real, se queda corto al momento.
3.1 worker_connections: calcular el techo
Fórmula de conexiones concurrentes máximas:
Concurrencia máxima = worker_processes × worker_connections
Con 8 núcleos, worker_processes en 8 (o auto) y worker_connections en 4096:
Concurrencia máxima = 8 × 4096 = 32768
Parece mucho, pero cada petición suele usar dos conexiones (cliente → Nginx, Nginx → backend). La concurrencia útil ronda la mitad.
Configuración en el bloque events:
events {
worker_connections 4096;
use epoll;
multi_accept on;
}
use epoll es el valor por defecto en Linux; escribirlo explícito aclara la intención. multi_accept on permite aceptar varias conexiones nuevas a la vez y reduce colas en picos.
3.2 keepalive del cliente: reutilizar conexiones
Establecer TCP cuesta (tres vías). keepalive reutiliza la conexión cliente–Nginx.
http {
keepalive_timeout 65;
keepalive_requests 1000;
}
keepalive_timeout 65: la conexión se mantiene 65 segundos. Demasiado alto consume recursos; demasiado bajo reduce la reutilización. 60-75 segundos es razonable.
keepalive_requests 1000: hasta 1000 peticiones por conexión. Muy bajo fuerza desconexiones frecuentes; muy alto puede favorecer fugas. 1000 es un valor estable en mis pruebas.
3.3 upstream keepalive: pool hacia el backend
Muchos no lo conocen, pero el efecto es claro. Nginx también puede reutilizar conexiones al backend y evitar abrir TCP en cada petición.
upstream backend {
server 127.0.0.1:8080;
server 127.0.0.1:8081;
keepalive 64;
keepalive_timeout 60s;
keepalive_requests 1000;
}
server {
location / {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Connection "";
}
}
keepalive 64: mantiene 64 conexiones inactivas en el pool. Ajusta según número de backends; suele ir 4-8 veces por servidor.
proxy_http_version 1.1 y proxy_set_header Connection "" son obligatorios. HTTP/1.1 soporta keepalive; vaciar Connection permite reutilizar. Sin esas dos líneas, upstream keepalive no funciona.
Comparación medida:
| Configuración | Conexiones nuevas/min | CPU | Escenario |
|---|---|---|---|
| Sin upstream keepalive | 6000 | Alta | Bajo tráfico |
| keepalive 32 | 3000 | Media | Tráfico medio |
| keepalive 64 | 1500 | Baja | Alto tráfico |
Con upstream keepalive, el coste de establecer conexiones cae un 50 %. En alta concurrencia es especialmente importante.
3.4 Tabla rápida de parámetros recomendados
Recomendaciones por escenario:
| Parámetro | Bajo (<1000 QPS) | Medio (1000-5000 QPS) | Alto (>5000 QPS) |
|---|---|---|---|
| worker_processes | auto | auto | auto |
| worker_connections | 1024 | 2048 | 4096 |
| keepalive_timeout | 60 | 65 | 75 |
| keepalive_requests | 100 | 500 | 1000 |
| upstream keepalive | 16 | 32 | 64 |
Es punto de partida; el ajuste fino requiere pruebas de carga. Uso wrk o ab, observo curvas de conexiones y latencia y busco el óptimo.
Capítulo 4: plantilla integrada — lista para producción
Los tres capítulos anteriores explican la teoría; aquí va una plantilla unificada. Ajusta parámetros según tu caso; el esqueleto es reutilizable.
# Plantilla nginx.conf para producción
user nginx;
worker_processes auto;
events {
worker_connections 4096;
use epoll;
multi_accept on;
}
http {
# Compresión gzip
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_min_length 1000;
gzip_types text/plain text/css text/xml text/javascript
application/json application/javascript application/xml
application/xml+rss application/xhtml+xml;
gzip_disable "msie6";
# Ruta de caché
proxy_cache_path /var/cache/nginx
levels=1:2
keys_zone=my_cache:10m
max_size=10g
inactive=60m
use_temp_path=off;
# Conexiones cliente
keepalive_timeout 65;
keepalive_requests 1000;
# Grupo de backends
upstream backend {
server 127.0.0.1:8080;
server 127.0.0.1:8081;
keepalive 64;
keepalive_timeout 60s;
keepalive_requests 1000;
}
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Connection "";
# Caché
proxy_cache my_cache;
proxy_cache_valid 200 302 10m;
proxy_cache_valid 404 1m;
proxy_cache_key $scheme$request_method$host$request_uri;
proxy_cache_use_stale error timeout http_500 http_502 http_503 http_504;
# Cabecera de depuración
add_header X-Cache-Status $upstream_cache_status;
}
}
}
Configuración diferenciada por escenario
E-commerce: portada y ficha de producto cambian a menudo; caché corta, 10-15 minutos. upstream keepalive grande: muchas consultas a base de datos y presión en el backend.
API: alta exigencia de tiempo real; proxy_cache_valid puede ser solo 1-5 minutos. gzip en JSON ayuda mucho; no lo omitas.
Sitio estático: HTML, CSS y JS casi fijos; caché de 1 hora o más. gzip rinde al máximo en texto.
Capítulo 5: problemas frecuentes y diagnóstico
Algunos casos que he visto a menudo, con solución directa.
P: gzip no funciona; no aparece Content-Encoding: gzip
Revisa tres puntos:
- ¿
gzip onestá en el nivel correcto (bloque http)? - ¿
gzip_typesincluye el MIME de tu respuesta? - ¿El tamaño supera
gzip_min_length?
Prueba con curl: curl -H "Accept-Encoding: gzip" -I http://your-site.com
P: baja tasa de aciertos; X-Cache-Status suele ser MISS
Causas habituales:
- Clave de caché mal diseñada; cada petición parece distinta
proxy_cache_validdemasiado corto- Cabeceras del backend con
Cache-Control: no-cacheoSet-Cookie
Revisa cabeceras de respuesta y confirma que no prohíban la caché.
P: worker_connections insuficiente; errores 502
En el log de errores de Nginx, si ves worker_connections are not enough, la concurrencia superó el límite.
Solución:
- Subir
worker_connections - Buscar fugas de conexión (keepalive mal configurado)
- Añadir servidores y balanceo de carga
P: uso de memoria alto; OOM frecuente
Posibles causas:
keys_zonedeproxy_cache_pathdemasiado grande- Demasiados archivos en caché y alto uso de mmap
- Pool keepalive muy grande; conexiones inactivas ocupan recursos
Reduce esos parámetros o añade memoria al servidor.
Para cerrar
El núcleo del ajuste de rendimiento en Nginx son tres palancas: compresión gzip, estrategia de caché y pool de conexiones. Bien combinadas, la carga puede duplicar velocidad y la concurrencia multiplicarse por tres o cuatro.
No es un trabajo de una sola vez. Recomiendo este orden:
- Primero gzip: cambio mínimo, beneficio inmediato, unos diez minutos
- Luego caché: según el negocio, en un día
- Por último el pool: requiere pruebas de carga; mejor con tráfico estable
Tras cada cambio, valida con pruebas de carga. wrk o ab sirven; mira latencia, QPS y tasa de error. No ajustes a ojo — usa datos.
Lista de comprobación final:
- gzip activo con MIME completos
- gzip_comp_level entre 4 y 6, equilibrio CPU y compresión
- proxy_cache_path configurado con tamaño razonable
- proxy_cache_valid acorde al negocio
- proxy_cache_use_stale para degradación
- worker_connections en 4096 o superior
- keepalive_timeout entre 60 y 75 segundos
- upstream keepalive con HTTP/1.1 y cabecera Connection
- X-Cache-Status en respuesta para depuración
Eso es todo. Si tienes dudas, déjalas en comentarios y responderé cuando pueda.
Flujo de ajuste de rendimiento en Nginx
Tres pasos para configurar en producción compresión gzip, estrategia de caché y optimización del pool de conexiones
⏱️ Estimated time: 30 min
- 1
Step 1: Activar compresión gzip
Añade en el bloque http:
• gzip on; activa la compresión
• gzip_vary on; añade la cabecera Vary
• gzip_comp_level 6; nivel de compresión (recomendado 4-6)
• gzip_min_length 1000; no comprimir archivos menores de 1 KB
• gzip_types especifica tipos MIME: text/plain text/css application/json application/javascript - 2
Step 2: Configurar caché proxy_cache
Configuración en dos pasos:
Paso 1: definir la ruta de caché
• proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=my_cache:10m max_size=10g inactive=60m
Paso 2: activar caché en location
• proxy_cache my_cache;
• proxy_cache_valid 200 10m; respuestas correctas en caché 10 minutos
• proxy_cache_use_stale error timeout http_502; estrategia de degradación - 3
Step 3: Optimizar parámetros del pool de conexiones
Tres configuraciones clave:
• worker_connections 4096; en el bloque events
• keepalive_timeout 65; keepalive_requests 1000; en el bloque http
• upstream keepalive 64; proxy_http_version 1.1; proxy_set_header Connection ""; configuración del pool hacia el backend
FAQ
¿Qué nivel de compresión gzip conviene elegir?
¿Cuándo usar proxy_cache y cuándo fastcgi_cache?
¿Cuánto conviene configurar worker_connections?
¿Cómo diagnosticar una baja tasa de aciertos en caché?
¿Por qué upstream keepalive exige HTTP/1.1?
¿Cómo fijar el tiempo de caché según el tipo de negocio?
11 min de lectura · Publicado el: 11 abr 2026 · Actualizado el: 21 ago 2026
Guía práctica de Nginx
Si llegaste desde búsqueda, lo más rápido es ir al artículo anterior o siguiente de esta misma serie.
Anterior
Guía completa del proxy inverso en Nginx: upstream, buffering y timeouts
Análisis profundo de las tres configuraciones clave del proxy inverso en Nginx: balanceo de carga upstream, ajuste de proxy buffer y ajuste de timeouts, con técnicas prácticas para diagnosticar errores 502/504, pool de conexiones keepalive y comprobaciones de salud
Parte 1 de 6
Siguiente
Nginx SSL/TLS en la práctica: del certificado HTTPS al endurecimiento A+
Configura Nginx HTTPS desde cero: certificados Let's Encrypt, endurecimiento TLS 1.3, plantilla A+ en SSL Labs, optimización OCSP Stapling. Guía completa actualizada para 2026.
Parte 3 de 6



Comentarios
Inicia sesión con GitHub para dejar un comentario