Parte de nuestra serie Performance & Scalability
Leer la guía completaConfiguración de producción de Nginx: SSL, almacenamiento en caché y seguridad
Nginx es el servidor web de producción que maneja prácticamente todas las implementaciones web de alto tráfico. Cuando se configura bien, proporciona terminación SSL, servicio eficiente de archivos estáticos, proxy WebSocket, limitación de velocidad y encabezados de seguridad, todo antes de que una sola solicitud llegue a su aplicación Node.js. Cuando se configura mal, se convierte en un cuello de botella, un problema de seguridad o la causa de misteriosos bucles de redireccionamiento de Cloudflare.
Esta guía cubre una configuración de Nginx de nivel de producción para una implementación de Node.js con múltiples aplicaciones: interfaz de Next.js, API de NestJS y documentos de Docusaurus, todos ejecutándose en el mismo servidor detrás de Cloudflare.
Conclusiones clave
- Nunca divida www y no www en bloques de servidores separados detrás de Cloudflare: provoca bucles de redireccionamiento
- Utilice un único archivo de configuración de Nginx en
/etc/nginx/conf.d/; no establezca también un enlace simbólico ensites-enabled/X-XSS-Protectionestá en desuso; use CSP en su lugar;X-Frame-Options: DENYsigue siendo válidoproxy_passdebe incluir la barra diagonal al utilizar un prefijo de ruta- Siempre vale la pena habilitar la compresión Gzip para tipos text/* MIME
- La limitación de velocidad requiere
limit_req_zonedefinido en el nivelhttp{}, no en el nivelserver{}- Los certificados Let's Encrypt se renuevan automáticamente con Certbot; agregue un trabajo cron para recargar Nginx después de la renovación
- El proxy WebSocket requiere encabezados específicos:
UpgradeyConnection
Estructura del directorio
Mantenga organizada la configuración de Nginx:
/etc/nginx/
nginx.conf — Main config (rarely touch this)
conf.d/
ecosire-production.conf — All your server blocks in ONE file
snippets/
ssl-params.snippet — SSL hardening (included by server blocks)
proxy-params.snippet — Common proxy headers
security-headers.snippet — Security headers
Crítico: Coloque toda su configuración en conf.d/ecosire-production.conf. NO lo agregue también a sites-enabled/; esto hace que Nginx procese la configuración dos veces, lo que genera errores duplicados de limit_req_zone y un comportamiento inesperado.
Zonas limitantes de velocidad
Defina zonas limitantes de velocidad en el nivel http{} en su configuración. No se pueden definir dentro de los bloques server{}:
# /etc/nginx/conf.d/ecosire-production.conf
# Must be at http{} level — these are in the main conf or conf.d root
limit_req_zone $binary_remote_addr zone=api_general:10m rate=60r/m;
limit_req_zone $binary_remote_addr zone=api_auth:10m rate=10r/m;
limit_req_zone $binary_remote_addr zone=api_public:10m rate=30r/m;
limit_req_zone $binary_remote_addr zone=static_files:10m rate=200r/m;
Bloque del servidor de aplicaciones principal
# Main web application — Next.js on port 3000
server {
listen 80;
listen [::]:80;
server_name ecosire.com www.ecosire.com;
# Cloudflare handles SSL termination — Nginx only sees HTTP
# (If direct SSL, add listen 443 ssl and certificate paths)
# Security headers
add_header X-Frame-Options "DENY" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
# CSP — adjust based on your needs
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' https://js.stripe.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; frame-src https://js.stripe.com; connect-src 'self' https://api.ecosire.com;" always;
# Remove server version from responses
server_tokens off;
# Gzip compression
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types
text/plain
text/css
text/javascript
application/javascript
application/json
application/xml
image/svg+xml
font/woff2;
gzip_min_length 1024;
# ─── Static Files: Next.js build output ─────────────────────────
location /_next/static/ {
proxy_pass http://127.0.0.1:3000;
add_header Cache-Control "public, max-age=31536000, immutable";
# Files include content hash in filename — safe to cache forever
}
# ─── Public static assets ────────────────────────────────────────
location /assets/ {
proxy_pass http://127.0.0.1:3000;
add_header Cache-Control "public, max-age=86400"; # 1 day
}
# ─── Well-known files (no locale prefix) ─────────────────────────
location /.well-known/ {
proxy_pass http://127.0.0.1:3000;
add_header Cache-Control "public, max-age=86400";
}
# ─── App routes (rate limited) ───────────────────────────────────
location / {
limit_req zone=api_general burst=20 nodelay;
limit_req_status 429;
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
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_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
# Timeouts
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
}
Bloque de servidor API
# NestJS API — port 3001
server {
listen 80;
listen [::]:80;
server_name api.ecosire.com;
server_tokens off;
add_header X-Frame-Options "DENY" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# CORS — handled by NestJS, but Nginx can add preflight response
# for performance (avoids reaching Node.js for OPTIONS)
location / {
if ($request_method = 'OPTIONS') {
add_header Access-Control-Allow-Origin "https://ecosire.com";
add_header Access-Control-Allow-Methods "GET, POST, PUT, PATCH, DELETE, OPTIONS";
add_header Access-Control-Allow-Headers "Content-Type, Authorization";
add_header Access-Control-Allow-Credentials "true";
add_header Access-Control-Max-Age 1728000;
add_header Content-Length 0;
return 204;
}
limit_req zone=api_general burst=30 nodelay;
proxy_pass http://127.0.0.1:3001;
proxy_http_version 1.1;
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_set_header X-Forwarded-Proto $scheme;
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
# Stricter rate limits for auth endpoints
location ~ ^/api/auth/(login|exchange|callback) {
limit_req zone=api_auth burst=5 nodelay;
limit_req_status 429;
proxy_pass http://127.0.0.1:3001;
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_set_header X-Forwarded-Proto $scheme;
}
# Health check — no rate limiting
location /api/health {
proxy_pass http://127.0.0.1:3001;
proxy_set_header Host $host;
access_log off; # Don't log health check spam
}
# Stripe webhook — needs raw body
location /api/billing/webhook {
proxy_pass http://127.0.0.1:3001;
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_set_header X-Forwarded-Proto $scheme;
# No rate limiting on webhooks — Stripe IPs are trusted
# NestJS validates the Stripe signature
}
}
Proxy WebSocket
Si su aplicación utiliza WebSockets (Socket.IO, puerta de enlace NestJS WebSocket), la configuración del proxy necesita encabezados específicos:
# WebSocket upgrade handling
location /ws/ {
proxy_pass http://127.0.0.1:3001;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade"; # Capital U required
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
# WebSocket connections can be long-lived
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
}
Sin los encabezados Upgrade y Connection, el protocolo de enlace WebSocket del navegador falla con un error 426 Upgrade Required o 101 Switching Protocols.
SSL directo (sin Cloudflare)
Si no utiliza Cloudflare, maneje la terminación SSL en Nginx directamente:
server {
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
server_name ecosire.com;
ssl_certificate /etc/letsencrypt/live/ecosire.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/ecosire.com/privkey.pem;
# SSL hardening
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8 8.8.4.4 valid=300s;
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
# ... rest of server block
}
# HTTP to HTTPS redirect
server {
listen 80;
listen [::]:80;
server_name ecosire.com www.ecosire.com;
return 301 https://ecosire.com$request_uri;
}
Configuración específica de Cloudflare
Detrás de Cloudflare, Nginx solo ve las IP de Cloudflare. Para preservar las IP de clientes reales:
# Real IP from Cloudflare — add this in http{} or at top of server{}
set_real_ip_from 103.21.244.0/22;
set_real_ip_from 103.22.200.0/22;
set_real_ip_from 103.31.4.0/22;
set_real_ip_from 104.16.0.0/13;
set_real_ip_from 104.24.0.0/14;
set_real_ip_from 108.162.192.0/18;
set_real_ip_from 131.0.72.0/22;
set_real_ip_from 141.101.64.0/18;
set_real_ip_from 162.158.0.0/15;
set_real_ip_from 172.64.0.0/13;
set_real_ip_from 173.245.48.0/20;
set_real_ip_from 188.114.96.0/20;
set_real_ip_from 190.93.240.0/20;
set_real_ip_from 197.234.240.0/22;
set_real_ip_from 198.41.128.0/17;
set_real_ip_from 2400:cb00::/32;
set_real_ip_from 2606:4700::/32;
set_real_ip_from 2803:f800::/32;
set_real_ip_from 2405:b500::/32;
set_real_ip_from 2405:8100::/32;
set_real_ip_from 2a06:98c0::/29;
set_real_ip_from 2c0f:f248::/32;
real_ip_header CF-Connecting-IP;
Además: nunca divida www.ecosire.com y ecosire.com en bloques de servidor Nginx separados cuando use Cloudflare. Cloudflare maneja la redirección www en su borde. Si agrega un bloque de servidor www que redirige a sitios que no son www, y Cloudflare también redirige a www, obtendrá un bucle de redireccionamiento (ERR_TOO_MANY_REDIRECTS).
Configuración de registro
# Custom log format with useful fields
log_format json_combined escape=json
'{'
'"time":"$time_iso8601",'
'"remote_addr":"$remote_addr",'
'"cf_ip":"$http_cf_connecting_ip",'
'"method":"$request_method",'
'"uri":"$request_uri",'
'"status":$status,'
'"body_bytes":$body_bytes_sent,'
'"response_time":$request_time,'
'"referrer":"$http_referer",'
'"user_agent":"$http_user_agent"'
'}';
access_log /var/log/nginx/ecosire-access.log json_combined;
error_log /var/log/nginx/ecosire-error.log warn;
Probando su configuración
# Test syntax before applying
nginx -t
# Reload without downtime
nginx -s reload
# Check which config file is active
nginx -T | grep "configuration file"
# Test a specific server block
curl -I https://ecosire.com
curl -I https://api.ecosire.com/api/health
# Check security headers
curl -I https://ecosire.com | grep -E "X-Frame|X-Content|Referrer|Content-Security"
Preguntas frecuentes
¿Debo usar Nginx o Caddy para un nuevo proyecto?
Caddy es significativamente más sencillo de configurar: maneja Let's Encrypt SSL automáticamente y tiene valores predeterminados sensibles listos para usar. Nginx es más potente y tiene un ecosistema más amplio de módulos y documentación. Para la mayoría de proyectos nuevos, Caddy es el mejor punto de partida; cambie a Nginx si necesita un control detallado sobre SSL, enrutamiento ascendente complejo o módulos específicos de Nginx. Para servidores Linux existentes donde Nginx ya está instalado, quédese con Nginx.
¿Cómo evito el error duplicado limit_req_zone?
The limit_req_zone directive must appear at the http{} context level, not inside server{} blocks. En una implementación de Turborepo monorepo en la que incluye varios archivos de configuración, asegúrese de que la directiva aparezca solo una vez en todos los archivos incluidos. Si ve el error, verifique si tiene un enlace simbólico conf.d/app.conf y sites-enabled/ que apunte al mismo archivo.
¿Cómo configuro Nginx para Next.js ISR (regeneración estática incremental)?
Next.js maneja ISR internamente: Nginx solo necesita enviar todas las solicitudes a Next.js sin almacenar en caché las respuestas. No agregue encabezados Cache-Control que interfieran con los encabezados de caché ISR de Next.js. Para recursos estáticos en /_next/static/, agregue encabezados de caché immutable ya que estos archivos tienen nombres con contenido hash. Para todas las demás rutas, deje que Next.js configure los encabezados de la caché.
¿Por qué mi puntuación SSL disminuye cuando Cloudflare está habilitado?
Cuando Cloudflare está en modo proxy, SSL Labs prueba el SSL de Cloudflare, no el suyo. Configure el modo SSL de Cloudflare en "Completo (Estricto)" para garantizar que Cloudflare valide su certificado de origen. Instale un certificado de origen de Cloudflare en su servidor: es gratuito y válido por 15 años. Su puntuación de SSL Labs se convierte en la puntuación de Cloudflare (normalmente A+), lo que en realidad es una mejora con respecto a una configuración típica de Nginx SSL autoconfigurada.
¿Cómo manejo varias aplicaciones Node.js en el mismo servidor?
Ejecute cada aplicación en un puerto diferente (Next.js en 3000, NestJS en 3001, Docusaurus en 3002, etc.) y agregue un bloque de servidor para cada subdominio en su configuración de Nginx. Utilice PM2 para gestionar todos los procesos de Node.js. Nginx enruta por subdominio (o prefijo de ruta) al puerto correcto. Cada bloque de servidor puede tener su propia configuración de encabezado de seguridad, almacenamiento en caché y limitación de velocidad.
Próximos pasos
Una configuración de producción de Nginx es un documento vivo: evoluciona a medida que cambian los requisitos de su aplicación, sus patrones de tráfico y surgen nuevas mejores prácticas de seguridad. ECOSIRE ejecuta Nginx en producción para múltiples aplicaciones, manejando la terminación SSL, la limitación de velocidad y la integración de Cloudflare en todos los dominios.
Ya sea que necesite consultoría de DevOps, configuración de infraestructura de producción o un diseño completo de arquitectura de implementación, explore nuestros servicios para ver cómo podemos ayudarlo.
Escrito por
ECOSIRE TeamTechnical Writing
The ECOSIRE technical writing team covers Odoo ERP, Shopify eCommerce, AI agents, Power BI analytics, GoHighLevel automation, and enterprise software best practices. Our guides help businesses make informed technology decisions.
ECOSIRE
Haga crecer su negocio con ECOSIRE
Soluciones empresariales en ERP, comercio electrónico, inteligencia artificial, análisis y automatización.
Artículos relacionados
Modelo de Seguridad OpenClaw, Residencia de Datos, SOC 2 e ISO 27001
Arquitectura de seguridad OpenClaw: aislamiento de inquilinos, cifrado, gestión de secretos, registros de auditoría, residencia de datos, SOC 2, ISO 27001, GDPR, aptitud HIPAA.
Seguridad a nivel de fila de Power BI: patrones dinámicos frente a estáticos
Análisis profundo de Power BI RLS: roles estáticos frente a dinámicos, patrones USERPRINCIPALNAME, tablas de seguridad, jerarquías de administradores, pruebas de RLS y RLS integrado para SaaS.
Detección de fraude mediante IA para el comercio electrónico: proteja los ingresos sin bloquear las ventas
Implemente una detección de fraude mediante IA que detecte más del 95 % de las transacciones fraudulentas y mantenga las tasas de falsos positivos por debajo del 2 %. Puntuación de ML, análisis de comportamiento y guía de ROI.
Más de Performance & Scalability
Odoo 19 RRHH: Matriz de Habilidades, Planes de Carrera, Ciclos de Desempeño
Actualización de recursos humanos de Odoo 19: matriz de habilidades nativas, planificación de trayectoria profesional, ciclos de revisión del desempeño, cuadrícula de 9 casillas, planificación de sucesión, integración HRIS.
Puntos de referencia de rendimiento de Odoo 19: números de ajuste de PostgreSQL 17
Puntos de referencia de rendimiento de Odoo 19 en el mundo real: velocidad del cliente web, rendimiento de ORM, configuración de ajuste de PG17, agrupación de conexiones, recuento de trabajadores, umbrales de escala.
Optimización de costos de OpenClaw y eficiencia de tokens a escala
Optimización de costos de tokens OpenClaw: almacenamiento en caché de avisos, enrutamiento de modelos, almacenamiento en caché de respuestas, API por lotes y barreras de costos por inquilino para agentes de producción.
Actualización incremental de Power BI para tablas de más de 10 millones de filas
Guía de actualización incremental de Power BI para tablas de más de 10 millones de filas: diseño de particiones, RangeStart/RangeEnd, políticas de actualización, plegado de consultas e híbridos de DirectQuery.
Depuración y monitoreo de Webhook: la guía completa de solución de problemas
Domine la depuración de webhooks con esta guía completa que cubre patrones de falla, herramientas de depuración, estrategias de reintento, paneles de monitoreo y mejores prácticas de seguridad.
Prueba de carga de k6: pruebe sus API antes del lanzamiento
Domine las pruebas de carga de k6 para las API de Node.js. Cubre aumentos de usuarios virtuales, umbrales, escenarios, HTTP/2, pruebas de WebSocket, paneles de Grafana y patrones de integración de CI.