Optimización del rendimiento de Nginx en un VPS
Optimiza Nginx para tráfico en producción en un VPS Linux. Worker processes, compresión, caché, HTTP/2, TLS y parámetros del kernel con benchmarks wrk.
Una instalación de Nginx por defecto maneja tráfico moderado sin problemas. Pero los valores predeterminados son conservadores. En un VPS con 4 vCPU y 8 GB de RAM, puedes servir muchas más peticiones por segundo ajustando los worker processes, la compresión, la caché y los parámetros del kernel. Esta guía recorre cada capa, con benchmarks que demuestran que cada cambio importa.
Asumimos que Nginx ya está instalado y sirviendo tráfico. Si no es así, empieza con la guía de administración de Nginx en un VPS.
Todos los ejemplos apuntan a Nginx mainline en Debian 12 o Ubuntu 24.04. La estructura de archivos de configuración se cubre en la guía sobre la estructura de archivos de configuración de Nginx.
¿Cómo establecer una línea base de rendimiento para Nginx?
Antes de cambiar nada, mide el rendimiento actual con wrk. Esto te da una línea base para comparar tras el tuning. Sin números, estás adivinando.
Instala wrk en una máquina separada (tu equipo local u otro VPS). Nunca hagas benchmarks desde el mismo servidor que estás probando. La herramienta de benchmark y el servidor web compiten por CPU, y los resultados pierden sentido.
apt install wrk
Ejecuta un test de 30 segundos con 4 hilos y 200 conexiones contra una página estática:
wrk -t4 -c200 -d30s https://your-server.example.com/
Running 30s test @ https://your-server.example.com/
4 threads and 200 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 12.34ms 5.67ms 89.12ms 78.45%
Req/Sec 4.12k 312.45 5.23k 72.50%
493440 requests in 30.01s, 1.92GB read
Requests/sec: 16442.18
Transfer/sec: 65.52MB
Anota cuatro números: requests/sec, latencia media, latencia máxima y transfer/sec. Esa es tu línea base.
¿Cuántos worker processes y conexiones debería usar Nginx?
Configura worker_processes auto para crear un worker por núcleo de CPU. En un VPS con 4 vCPU, esto significa 4 workers. Cada worker es mono-hilo y gestiona conexiones de forma independiente mediante epoll. Un worker por núcleo evita la sobrecarga del cambio de contexto.
La fórmula para el máximo de conexiones simultáneas:
max conexiones = worker_processes x worker_connections
| vCPUs | worker_processes | worker_connections | Max conexiones |
|---|---|---|---|
| 1 | 1 | 2048 | 2.048 |
| 2 | 2 | 2048 | 4.096 |
| 4 | 4 | 2048 | 8.192 |
| 8 | 8 | 2048 | 16.384 |
Cada conexión consume un descriptor de archivo. Establece worker_rlimit_nofile más alto que worker_connections para evitar alcanzar el límite del SO. Un valor seguro es worker_connections * 2, que tiene en cuenta las conexiones upstream cuando se usa como proxy.
Edita /etc/nginx/nginx.conf:
worker_processes auto;
worker_cpu_affinity auto;
worker_rlimit_nofile 8192;
events {
worker_connections 4096;
use epoll;
multi_accept on;
}
worker_cpu_affinity auto fija cada worker a un núcleo de CPU, reduciendo los fallos de caché por migración de procesos. multi_accept on permite a un worker aceptar todas las conexiones pendientes de una vez en lugar de una a una. use epoll es el valor por defecto en Linux, pero vale la pena ser explícito.
La directiva accept_mutex está en off por defecto desde Nginx 1.11.3, ya que los kernels Linux 4.5+ soportan EPOLLEXCLUSIVE, que distribuye conexiones entre workers sin mutex. Déjala desactivada.
nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
systemctl reload nginx
¿Qué directivas TCP mejoran el throughput de Nginx?
Tres directivas trabajan juntas para optimizar cómo Nginx envía datos por TCP. sendfile evita el buffer en espacio de usuario copiando datos directamente entre descriptores de archivo en el kernel. tcp_nopush agrupa las cabeceras de respuesta y el inicio de un archivo en un solo paquete TCP. tcp_nodelay desactiva el algoritmo de Nagle para que los paquetes pequeños (como el final de una respuesta) se envíen inmediatamente.
Añádelas al bloque http en /etc/nginx/nginx.conf:
http {
sendfile on;
tcp_nopush on;
tcp_nodelay on;
#...directivas existentes...
}
sendfile importa más para servir archivos estáticos. Sin ella, Nginx lee el archivo en un buffer y luego escribe el buffer al socket. Dos copias. Con sendfile, el kernel hace una transferencia zero-copy. En un servidor de archivos estáticos con carga, esto solo ya reduce el uso de CPU de forma notable.
tcp_nopush y tcp_nodelay no son contradictorias. Nginx aplica tcp_nopush mientras construye la respuesta, luego cambia a tcp_nodelay para el último paquete. El resultado: menos paquetes en total, sin retardo en el último.
¿Cómo ajustar las conexiones keepalive en Nginx?
Las conexiones keepalive permiten a un cliente reutilizar una conexión TCP para varias peticiones HTTP. Esto evita la sobrecarga de handshakes TCP y negociación TLS en cada petición. Una sola carga de página puede disparar 20-50 subpeticiones para CSS, JS, imágenes y fuentes.
http {
keepalive_timeout 65;
keepalive_requests 1000;
#...directivas existentes...
}
keepalive_timeout 65 cierra conexiones inactivas tras 65 segundos. Demasiado alto desperdicia descriptores de archivo en clientes inactivos. Demasiado bajo fuerza reconexiones. 65 segundos es un valor razonable para la mayoría de cargas de trabajo.
keepalive_requests 1000 permite hasta 1.000 peticiones por conexión antes de que Nginx la cierre. El valor por defecto es 1000 desde Nginx 1.19.10. Si estás detrás de un balanceador de carga que envía muchas peticiones por conexión, súbelo a 10000.
Keepalive upstream
Si Nginx actúa como proxy hacia un backend (Node.js, Python, Go), las conexiones keepalive upstream evitan que Nginx abra una nueva conexión TCP al backend en cada petición. Aquí es donde la mayoría de configuraciones proxy pierden tiempo.
upstream backend {
server 127.0.0.1:3000;
keepalive 64;
}
server {
location / {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Connection "";
}
}
keepalive 64 mantiene 64 conexiones inactivas abiertas al backend por worker. proxy_http_version 1.1 es necesario porque keepalive es una funcionalidad de HTTP/1.1. La cabecera Connection vacía elimina el Connection: close del cliente para que Nginx no lo reenvíe al upstream.
Para más detalles sobre la configuración de proxy, consulta la guía sobre reverse proxy con Nginx.
¿Cómo dimensionar los buffers de proxy en Nginx?
Cuando Nginx actúa como proxy, almacena en buffer la respuesta del backend. Si el buffer es demasiado pequeño, Nginx escribe la respuesta en un archivo temporal en disco. La E/S de disco es órdenes de magnitud más lenta que la RAM.
http {
proxy_buffer_size 16k;
proxy_buffers 4 32k;
proxy_busy_buffers_size 64k;
client_body_buffer_size 16k;
client_header_buffer_size 1k;
large_client_header_buffers 4 8k;
#...directivas existentes...
}
proxy_buffer_size 16k gestiona las cabeceras de respuesta del backend. La mayoría de cabeceras caben en 4k-8k, pero aplicaciones que establecen muchas cookies o cabeceras personalizadas necesitan más. 16k es seguro sin ser despilfarrador.
proxy_buffers 4 32k asigna 4 buffers de 32k cada uno (128k en total por conexión) para el cuerpo de la respuesta. Dimensiónalos según el tamaño típico de tus respuestas. Las respuestas de API de menos de 100k encajan cómodamente. Si sirves payloads grandes, aumenta el número de buffers en lugar del tamaño.
proxy_busy_buffers_size 64k controla cuántos datos en buffer puede enviar Nginx al cliente mientras sigue leyendo del backend. No debería superar el total de proxy_buffers.
Vigila esto en tu log de errores:
an upstream response is buffered to a temporary file
Si lo ves con frecuencia, aumenta proxy_buffers. Revisa el log:
journalctl -u nginx --no-pager | grep "temporary file"
¿Cómo configurar la caché de archivos estáticos en Nginx?
La caché de archivos estáticos indica a los navegadores que almacenen assets localmente. Esto elimina por completo las peticiones repetidas. Para assets con nombres hasheados (como app.a1b2c3.js), usa una expiración agresiva. Para HTML, mantenla corta.
server {
location ~* \.(css|js|woff2|woff|ttf|svg)$ {
expires 1y;
add_header Cache-Control "public, immutable";
access_log off;
}
location ~* \.(jpg|jpeg|png|gif|ico|webp|avif)$ {
expires 30d;
add_header Cache-Control "public";
access_log off;
}
location ~* \.html$ {
expires 1h;
add_header Cache-Control "public, no-cache";
}
}
immutable indica al navegador que no revalide el asset en absoluto. Úsalo solo para nombres de archivo con fingerprint. access_log off en assets estáticos reduce la E/S de disco por logging.
Para las cabeceras de caché y su interacción con las cabeceras de seguridad, consulta.
open_file_cache
Nginx puede cachear descriptores de archivo, tiempos de modificación y comprobaciones de existencia para archivos de acceso frecuente. Esto evita llamadas al sistema stat() y open() repetidas.
http {
open_file_cache max=10000 inactive=30s;
open_file_cache_valid 60s;
open_file_cache_min_uses 2;
open_file_cache_errors on;
}
max=10000 almacena hasta 10.000 entradas. Dimensiona esto según el número de archivos estáticos que sirves. Si sirves 500 archivos, max=1000 es suficiente. Si sirves 50.000 assets desde un origen CDN, auméntalo.
inactive=30s elimina entradas no accedidas en 30 segundos. open_file_cache_min_uses 2 solo cachea archivos accedidos al menos dos veces durante la ventana inactive. Esto evita que peticiones puntuales contaminen la caché.
open_file_cache_errors on también cachea las búsquedas 404. Si un cliente sigue pidiendo un archivo inexistente, Nginx responde desde la caché en lugar de consultar el sistema de archivos cada vez.
¿Cómo activar la compresión gzip y Brotli en Nginx?
Gzip en nivel 4-6 ofrece la mejor relación entre coste de CPU y ratio de compresión. Por encima del nivel 6 se gana menos de un 2 % de compresión duplicando el tiempo de CPU. Brotli en nivel 4 logra típicamente mejor compresión que gzip en nivel 9, con un coste de CPU similar.
Gzip
http {
gzip on;
gzip_comp_level 5;
gzip_min_length 256;
gzip_vary on;
gzip_proxied any;
gzip_types
text/plain
text/css
text/javascript
application/javascript
application/json
application/xml
application/xml+rss
image/svg+xml;
}
gzip_min_length 256 omite archivos menores de 256 bytes. Comprimir archivos diminutos puede producir una salida mayor que la entrada por las cabeceras gzip. gzip_vary on añade una cabecera Vary: Accept-Encoding para que las cachés almacenen versiones comprimidas y sin comprimir por separado. gzip_proxied any comprime respuestas incluso cuando la petición llegó a través de un proxy.
Brotli
Brotli ofrece un 15-25 % mejor compresión que gzip en assets de texto. Lo soportan todos los navegadores modernos. En Ubuntu 24.04 con el paquete Nginx de la distribución, instala el módulo directamente:
apt install libnginx-mod-http-brotli-filter libnginx-mod-http-brotli-static
En Debian 12 o al usar Nginx mainline desde nginx.org, el módulo Brotli no está incluido. Necesitas compilarlo como módulo dinámico o usar un repositorio de terceros. El repositorio ngx_brotli tiene las instrucciones de compilación.
Una vez cargado el módulo, configúralo:
http {
brotli on;
brotli_comp_level 4;
brotli_static on;
brotli_types
text/plain
text/css
text/javascript
application/javascript
application/json
application/xml
image/svg+xml;
}
brotli_static on sirve archivos precomprimidos .br si existen. Esto te permite comprimir assets en tiempo de build con un nivel de compresión más alto (p. ej., 11) sin pagar el coste de CPU en tiempo de ejecución.
brotli_comp_level 4 es el punto óptimo para compresión dinámica. A diferencia de gzip, los niveles 1-4 de Brotli son rápidos. Los niveles 5+ se vuelven mucho más lentos.
Comparación de compresión
| Tipo de contenido | gzip nivel 5 | Brotli nivel 4 | Ganador |
|---|---|---|---|
| HTML | 72 % | 78 % | Brotli |
| CSS | 80 % | 85 % | Brotli |
| JavaScript | 75 % | 82 % | Brotli |
| JSON | 78 % | 83 % | Brotli |
Los ratios representan bytes ahorrados respecto al original. Brotli gana consistentemente por 5-8 puntos porcentuales.
Ambos módulos pueden funcionar simultáneamente. Nginx sirve Brotli a los clientes que anuncian Accept-Encoding: br y recurre a gzip para el resto.
¿Cómo optimizar el rendimiento de TLS y HTTP/2 en Nginx?
TLS añade latencia por los handshakes y el intercambio de claves. La caché de sesión, OCSP stapling y TLS 1.3 minimizan esa sobrecarga. HTTP/2 multiplexa peticiones sobre una sola conexión, eliminando el head-of-line blocking a nivel HTTP.
HTTP/2
Desde Nginx 1.25.1, el parámetro http2 en la directiva listen está obsoleto. Usa la directiva http2 en su lugar:
server {
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
#...
}
Rendimiento TLS
http {
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_session_tickets off;
ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /etc/letsencrypt/live/example.com/chain.pem;
resolver 127.0.0.53 valid=300s;
resolver_timeout 5s;
}
ssl_session_cache shared:SSL:10m almacena parámetros de sesión TLS en una zona de memoria compartida de 10 MB. Un megabyte contiene unas 4.000 sesiones. Los clientes que vuelven se saltan el handshake TLS completo y retoman con una operación mucho más barata.
ssl_session_tickets off es el valor más seguro. Los session tickets usan una clave simétrica que, si se compromete, descifra todas las sesiones pasadas (sin forward secrecy). Si necesitas tickets para configuraciones multi-servidor, rota las claves con frecuencia.
ssl_stapling on hace que Nginx obtenga y cachee la respuesta OCSP de tu CA, incluyéndola en el handshake TLS. El cliente no necesita contactar la CA por separado. Esto ahorra 100-300 ms en la primera conexión.
ssl_prefer_server_ciphers off es correcto para TLS 1.3, donde cliente y servidor negocian los ciphers de forma diferente. Para compatibilidad con TLS 1.2, los ciphers seleccionados siguen importando, pero las suites de cifrado de TLS 1.3 son todas fuertes.
Para la configuración TLS completa con Let's Encrypt, consulta la guía de TLS y Let's Encrypt para Nginx.
¿Qué parámetros del kernel Linux mejoran el rendimiento de Nginx?
Cuatro parámetros del kernel limitan la capacidad de Nginx para manejar grandes volúmenes de conexiones. Los valores por defecto son conservadores para un servidor de propósito general. Ajustarlos elimina cuellos de botella a nivel de SO.
| Parámetro | Por defecto | Recomendado | Por qué |
|---|---|---|---|
net.core.somaxconn |
4096 | 65535 | Cola máxima del listen backlog. Valores bajos causan caída de conexiones en picos de tráfico. |
fs.file-max |
~100000 | 500000 | Límite de descriptores de archivo del sistema. Cada conexión es un descriptor de archivo. |
net.ipv4.tcp_tw_reuse |
0 | 1 | Reutiliza sockets en TIME_WAIT para nuevas conexiones. Acelera el reciclaje de conexiones. |
net.ipv4.tcp_fastopen |
0 | 3 | Activa TCP Fast Open para cliente y servidor. Ahorra un round trip en nuevas conexiones. |
net.ipv4.ip_local_port_range |
32768 60999 | 1024 65535 | Amplía el rango de puertos efímeros para conexiones salientes (proxy/upstream). |
net.core.netdev_max_backlog |
1000 | 16384 | Longitud de cola para paquetes entrantes cuando la interfaz recibe más rápido de lo que el kernel procesa. |
Aplica en caliente sin reinicio:
sysctl -w net.core.somaxconn=65535
sysctl -w fs.file-max=500000
sysctl -w net.ipv4.tcp_tw_reuse=1
sysctl -w net.ipv4.tcp_fastopen=3
sysctl -w net.ipv4.ip_local_port_range="1024 65535"
sysctl -w net.core.netdev_max_backlog=16384
Hazlos persistentes tras reinicio:
cat > /etc/sysctl.d/99-nginx-tuning.conf << 'EOF'
net.core.somaxconn = 65535
fs.file-max = 500000
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_fastopen = 3
net.ipv4.ip_local_port_range = 1024 65535
net.core.netdev_max_backlog = 16384
EOF
sysctl -p /etc/sysctl.d/99-nginx-tuning.conf
net.core.somaxconn = 65535
fs.file-max = 500000
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_fastopen = 3
net.ipv4.ip_local_port_range = 1024 65535
net.core.netdev_max_backlog = 16384
Aumenta también el límite de descriptores de archivo por proceso para la unidad systemd de Nginx. Crea un override:
mkdir -p /etc/systemd/system/nginx.service.d
cat > /etc/systemd/system/nginx.service.d/limits.conf << 'EOF'
[Service]
LimitNOFILE=65535
EOF
systemctl daemon-reload
systemctl restart nginx
cat /proc/$(pgrep -f 'nginx: master')/limits | grep "open files"
Max open files 65535 65535 files
¿Cómo optimizar el rendimiento del log de acceso?
Escribir una línea de log por cada petición consume E/S de disco. En servidores con mucho tráfico, el logging de acceso puede convertirse en un cuello de botella. El logging con buffer escribe en disco por lotes.
http {
access_log /var/log/nginx/access.log combined buffer=64k flush=5s;
}
buffer=64k acumula entradas de log en un buffer de memoria de 64 KB. flush=5s escribe el buffer a disco al menos cada 5 segundos, incluso si no está lleno. Se intercambian unos segundos de retraso en los logs por mucha menos E/S de disco.
Si no necesitas logs de acceso para assets estáticos (imágenes, CSS, JS), desactívalos por location como se muestra en la sección de caché anterior.
¿Cuánto más rápido es un Nginx optimizado?
Ejecuta el mismo benchmark wrk después de aplicar todos los cambios. Prueba desde la misma máquina, con los mismos parámetros:
wrk -t4 -c200 -d30s https://your-server.example.com/
Running 30s test @ https://your-server.example.com/
4 threads and 200 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 5.21ms 2.34ms 42.56ms 81.23%
Req/Sec 9.78k 478.12 11.42k 68.75%
1171200 requests in 30.02s, 4.56GB read
Requests/sec: 39013.66
Transfer/sec: 155.61MB
Antes vs. después
| Métrica | Antes | Después | Cambio |
|---|---|---|---|
| Requests/sec | 16.442 | 39.014 | +137 % |
| Latencia media | 12,34 ms | 5,21 ms | -58 % |
| Latencia máx. | 89,12 ms | 42,56 ms | -52 % |
| Transfer/sec | 65,52 MB | 155,61 MB | +137 % |
Estos números provienen de un VPS Virtua Cloud con 4 vCPU, 8 GB de RAM con Debian 12 y Nginx mainline, sirviendo una página HTML estática con assets CSS y JavaScript. Tus resultados variarán según la carga, las condiciones de red y si usas proxy a un backend.
Las mayores ganancias vienen de sysctl del kernel (elimina cuellos de botella del SO), tuning de workers/conexiones (usa toda la CPU disponible) y compresión (reduce bytes en la red). La caché de sesión TLS y HTTP/2 tienen un efecto menor pero medible, especialmente en la latencia de primera conexión.
Configuración optimizada completa
Archivo /etc/nginx/nginx.conf completo con todo el tuning aplicado:
user www-data;
pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;
worker_processes auto;
worker_cpu_affinity auto;
worker_rlimit_nofile 8192;
events {
worker_connections 4096;
use epoll;
multi_accept on;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
# Optimización TCP
sendfile on;
tcp_nopush on;
tcp_nodelay on;
# Keepalive
keepalive_timeout 65;
keepalive_requests 1000;
# Buffers
proxy_buffer_size 16k;
proxy_buffers 4 32k;
proxy_busy_buffers_size 64k;
client_body_buffer_size 16k;
client_header_buffer_size 1k;
large_client_header_buffers 4 8k;
# Caché de archivos
open_file_cache max=10000 inactive=30s;
open_file_cache_valid 60s;
open_file_cache_min_uses 2;
open_file_cache_errors on;
# Gzip
gzip on;
gzip_comp_level 5;
gzip_min_length 256;
gzip_vary on;
gzip_proxied any;
gzip_types
text/plain
text/css
text/javascript
application/javascript
application/json
application/xml
application/xml+rss
image/svg+xml;
# Brotli (si el módulo está instalado)
# brotli on;
# brotli_comp_level 4;
# brotli_static on;
# brotli_types text/plain text/css text/javascript
# application/javascript application/json application/xml
# image/svg+xml;
# TLS
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_session_tickets off;
ssl_stapling on;
ssl_stapling_verify on;
# Logging
access_log /var/log/nginx/access.log combined buffer=64k flush=5s;
error_log /var/log/nginx/error.log warn;
# Ocultar versión
server_tokens off;
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
}
server_tokens off oculta la versión de Nginx en las cabeceras de respuesta y páginas de error. La divulgación de versión ayuda a los atacantes a apuntar a vulnerabilidades conocidas.
¿Algo salió mal?
Revisa primero el log de errores:
journalctl -u nginx -f
Problemas comunes tras el tuning:
- "too many open files" en el log de errores:
worker_rlimit_nofilees menor queworker_connections, o elLimitNOFILEde systemd no está configurado. Revisa ambos. - "could not build optimal types_hash": aumenta
types_hash_max_sizea 4096 en el bloquehttp. - El módulo Brotli no carga: ejecuta
nginx -V 2>&1 | grep brotlipara comprobar si el módulo está compilado. Si usas módulos dinámicos, verifica que las directivasload_moduleestén al inicio denginx.conf. - OCSP stapling no funciona: la primera petición tras iniciar Nginx no tendrá respuesta stapled. Prueba con
openssl s_client -connect your-server:443 -status < /dev/null 2>&1 | grep -A 2 "OCSP Response". Si muestra "no response sent", verifica quessl_trusted_certificateapunte a la cadena completa yresolveresté configurado. - wrk no muestra mejora: asegúrate de probar desde una máquina diferente. Si pruebas por internet, la latencia de red domina y oculta las mejoras del servidor. Prueba desde un VPS en el mismo centro de datos para obtener cifras precisas.
Copyright 2026 Virtua.Cloud. Todos los derechos reservados. Este contenido es una obra original del equipo de Virtua.Cloud. La reproducción, republicación o redistribución sin permiso escrito está prohibida.
¿Listo para probarlo?
Despliega tu propio servidor en segundos. Linux, Windows o FreeBSD.
Ver planes VPS