TL;DR (visión rápida)
- No copies valores extremos de un Gist de pruebas a producción (p. ej.,
keepalive_requests 100000
, timeouts de minutos o buffers gigantes). - Ajusta por evidencia (tests + métricas) y por caso de uso (estático, API, proxy, TLS/HTTP2/3).
- Ten en cuenta la capa delante de NGINX (CDN/WAF/LB) para timeouts y real IP; configura rate limiting con
X-Forwarded-For
si hay proxy. - Límites del sistema (FDs,
nofile
) ysystemd
importan más que 20 “micro-tweaks”.
Principales aciertos del Gist (y cómo aterrizarlos)
worker_processes auto;
✅ perfecto hoy en día.worker_connections
alto ✅ sí, pero mide: 4.000 puede estar bien para APIs sin TLS pesado; con TLS/HTTP2 no subas a ciegas.sendfile on; tcp_nopush on; tcp_nodelay on;
✅ buenos defaults (estático).open_file_cache
✅ útil en sitios estáticos con muchos ficheros; prueba y monitoriza “cache hit/miss”.- Rate limiting (
limit_req
,limit_conn
) ✅ pero atrás de proxy debes leerX-Forwarded-For
/proxy_protocol
(ver abajo).
Cosas que en producción conviene moderar
keepalive_requests 100000;
❌ demasiado. Usa 100–1000 según patrón.send_timeout 2;
❌ agresivo (corta clientes lentos). Empieza en 10–15s.client_*_timeout
en minutos ❌ (para cargas normales). Mantén 15–60s según caso.- Buffers enormes (
client_header_buffer_size 3m
) ❌ pueden abrir DoS de memoria. Mantén defaults salvo cabeceras realmente grandes.
Matices “que casi nadie cuenta”
- Con Cloudflare plan free (y muchos ALB), el backend tiene ≈100s antes de 524; no uses timeouts superiores.
- Gzip on-the-fly aumenta CPU; mejor
gzip_static on;
(precomprimir) o Brotli/Zstd si tienes módulos. - HTTP/2 casi siempre sí; HTTP/3/QUIC opcional: activa si tu tráfico móvil/global lo necesita y tu LB lo soporta.
- BBR (kernel) puede mejorar latencia/TTFB para conexiones de alto RTT; pruébalo, no lo fuerces.
Plantilla base “production-safe” (servidor web/proxy TLS)
Sustituye rutas, dominios y upstreams. Comenta/activa HTTP/3 si procede.
# /etc/nginx/nginx.conf
user nginx;
worker_processes auto;
worker_rlimit_nofile 200000;
error_log /var/log/nginx/error.log warn;
pid /run/nginx.pid;
events {
worker_connections 4096;
# Linux moderno: epoll por defecto
# Optimiza aceptación en escenarios de alta concurrencia
multi_accept on;
# En kernels nuevos puedes usar SO_REUSEPORT (por bloque 'listen reuseport' en server)
}
http {
include mime.types;
default_type application/octet-stream;
# Logs: acceso bufferizado; desactívalo sólo en pruebas sintéticas
access_log /var/log/nginx/access.log main buffer=32k flush=1s;
# Rendimiento básico E/S
sendfile on;
tcp_nopush on; # cabeceras en un solo paquete
tcp_nodelay on;
# Keepalive sensato
keepalive_timeout 15s;
keepalive_requests 500;
# Timeouts conservadores
client_body_timeout 30s;
client_header_timeout 15s;
send_timeout 15s;
reset_timedout_connection on;
# Cache de descriptores de archivo
open_file_cache max=100000 inactive=20s;
open_file_cache_valid 30s;
open_file_cache_min_uses 2;
open_file_cache_errors on;
# Compresión (usa estática si puedes)
gzip on;
gzip_comp_level 2;
gzip_min_length 1024;
gzip_vary on;
gzip_disable "msie6";
gzip_types
text/plain text/css text/xml application/xml
application/json application/javascript application/rss+xml
image/svg+xml;
# gzip_static on; # si precomprimes .gz en build
# Seguridad básica
server_tokens off;
# Real IP si hay LB/CDN delante
# set_real_ip_from 10.0.0.0/8; # rangos de tu LB
# real_ip_header X-Forwarded-For;
# real_ip_recursive on;
# Limitar abusos (lee IP real si hay proxy)
limit_conn_zone $binary_remote_addr zone=conn_ip:10m;
limit_req_zone $binary_remote_addr zone=req_ip:10m rate=10r/s;
# TLS perfil moderno (ajusta a tu parque)
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers 'TLS_AES_256_GCM_SHA384:TLS_AES_128_GCM_SHA256:TLS_CHACHA20_POLY1305_SHA256:HIGH:!aNULL:!MD5';
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:50m;
ssl_session_timeout 1d;
ssl_session_tickets off;
# OCSP stapling si tienes chain y DNS correcto
# ssl_stapling on; ssl_stapling_verify on;
# HTTP/2 (HTTP/3 opcional en server)
# ssl_ecdh_curve X25519:secp384r1;
# Servidor de ejemplo estático
server {
listen 80;
listen 443 ssl http2; # añade http3 si procede: 'http3 reuseport'
server_name ejemplo.tld;
# Certs
ssl_certificate /etc/ssl/certs/fullchain.pem;
ssl_certificate_key /etc/ssl/private/privkey.pem;
# Límites por IP (ajusta a tu caso)
limit_conn conn_ip 20;
limit_req zone=req_ip burst=20 nodelay;
# Archivos estáticos
root /var/www/html;
index index.html;
location / {
try_files $uri $uri/ =404;
}
# Cabeceras de seguridad (ajusta CSP a tu app)
add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;
add_header Referrer-Policy no-referrer-when-downgrade;
add_header Permissions-Policy "geolocation=(), microphone=()";
# add_header Content-Security-Policy "default-src 'self';" always;
# Buffering de respuestas ascendentes (proxy) – descomenta si haces reverse proxy
# proxy_buffering on;
# proxy_buffers 16 16k;
# proxy_busy_buffers_size 32k;
# HTTP/3 (QUIC) opcional (requiere nginx quic build + firewall UDP 443)
# listen 443 quic reuseport;
# add_header Alt-Svc 'h3=":443"; ma=86400' always;
}
}
Lenguaje del código: PHP (php)
¿Reverse proxy/API? añade bloque
upstream
,proxy_set_header Host $host;
,proxy_set_header X-Real-IP $remote_addr;
,proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
,proxy_http_version 1.1;
yproxy_read_timeout
/send_timeout
acordes a tu backend (y a tu CDN/LB).
Rate limiting detrás de un proxy/CDN
Si tienes CDN o LB, no uses $binary_remote_addr
(verás la IP del proxy). Configura la IP real:
set_real_ip_from 103.21.244.0/22; # rangos CF/LB
set_real_ip_from 103.22.200.0/22;
real_ip_header X-Forwarded-For;
real_ip_recursive on;
map $http_x_forwarded_for $client_ip {
~^(?P<addr>\d+\.\d+\.\d+\.\d+) $addr;
default $remote_addr;
}
limit_req_zone $client_ip zone=req_ip:10m rate=10r/s;
limit_conn_zone $client_ip zone=conn_ip:10m;
Lenguaje del código: PHP (php)
Límites de ficheros (FD) y systemd
(Linux)
Aumenta nofile
vía systemd, no solo en nginx.conf
:
sudo mkdir -p /etc/systemd/system/nginx.service.d
cat <<'EOF' | sudo tee /etc/systemd/system/nginx.service.d/limits.conf
[Service]
LimitNOFILE=300000
EOF
sudo systemctl daemon-reload
sudo systemctl restart nginx
Lenguaje del código: JavaScript (javascript)
SELinux: si necesitas setrlimit
, setsebool -P httpd_setrlimit 1
.
HTTP/2, HTTP/3 y TLS
- HTTP/2: activa en
listen 443 ssl http2;
. - HTTP/3/QUIC: compila NGINX con QUIC o usa build que lo incluya; abre UDP/443 y añade
Alt-Svc
. - TLS: usa TLS1.2/1.3, desactiva
session_tickets
, usa curvas modernas. Revisa tu parque (clientes antiguos) antes de endurecer al máximo.
Brotli / Zstd vs Gzip
- Producción: precomprime estáticos (
gzip_static on;
o Brotli estático) en build; reduce CPU y mejora TTFB. - Dinámico: si comprimes on-the-fly, usa niveles bajos (1–2) y mide CPU. Brotli nivel 3–5 puede ser rentable en HTML/CSS/JS.
BBR (kernel) y colas
- Habilítalo si tu tráfico es global/móvil (alto RTT):
modprobe tcp_bbr
echo 'tcp_bbr' | sudo tee /etc/modules-load.d/bbr.conf
cat <<'EOF' | sudo tee /etc/sysctl.d/99-bbr.conf
net.ipv4.tcp_congestion_control=bbr
net.core.default_qdisc=fq
EOF
sudo sysctl --system
Lenguaje del código: PHP (php)
Checklist de no-hacer en producción
- ❌
keepalive_requests 100000
y timeouts de minutos “porque sí”. - ❌ Buffers gigantes “por si acaso”: abres superficie DoS de memoria.
- ❌ Rate limiting con IP del proxy (sin
real_ip_*
). - ❌ Desactivar logs “siempre”: úsalos bufferizados; necesitas trazabilidad.
- ❌ Copiar sin medir: ab/wrk/vegeta + métricas (CPU, latencia p50/p95/p99, conexiones activas).
Probar y desplegar
nginx -t # valida sintaxis
sudo systemctl reload nginx # reload sin cortar conexiones
# si falla, rollback al conf previo
Lenguaje del código: PHP (php)
Herramientas: wrk
/ vegeta
(carga), ss -s
(sockets), netstat -anp | grep nginx
, ngxtop
, Prometheus + exporter.
Conclusión
El Gist de denji es una buena base de laboratorio, pero en producción manda la observación: empieza con valores sanos, mide y ajusta. Si me dices tu caso (estático pesado/API/TLS+HTTP2/3, detrás de CDN o no, tráfico pico/medio), te dejo los valores iniciales más ajustados a tu escenario y una plantilla específica.
vía: GitHub