Generador de configuración de Nginx (reverse proxy, estático, load balancing, PHP-FPM)
Armá un server{} de Nginx listo para producción para cuatro casos de uso distintos: reverse proxy con rate limiting/auth/headers de seguridad, sitios estáticos con SPA fallback y cacheo, load balancing entre varios backends, o PHP-FPM al estilo WordPress. Con SSL vía Certbot en todos los modos.
# Redirige todo el tráfico HTTP a HTTPS.
server {
listen 80;
listen [::]:80;
server_name miapp.com;
location / {
return 301 https://$host$request_uri;
}
}
server {
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
server_name miapp.com;
ssl_certificate /etc/letsencrypt/live/miapp.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/miapp.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305;
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
client_max_body_size 10m;
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types text/plain text/css text/xml application/json application/javascript application/xml+rss application/atom+xml image/svg+xml;
location / {
proxy_pass http://127.0.0.1:3000;
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;
}
}
¿Qué es un reverse proxy y por qué usar uno?
Un reverse proxy es un servidor (acá, Nginx) que recibe todas las conexiones externas en un puerto público (80/443) y las reenvía hacia tu aplicación real, que corre en un puerto interno (por ejemplo 127.0.0.1:3000). Esto te permite tener SSL centralizado, servir varias apps distintas bajo el mismo puerto 443 por nombre de dominio, y agregar compresión o cacheo sin tocar el código de tu app.
El gotcha de proxy_set_header y WebSocket
Sin proxy_set_header X-Real-IP y X-Forwarded-For, tu aplicación ve TODAS las conexiones como si vinieran de 127.0.0.1 (la IP de Nginx) — rompe silenciosamente cualquier lógica que dependa de la IP real del cliente (rate-limiting, geolocalización, logs). Y sin el trío proxy_http_version 1.1 + Upgrade + Connection "upgrade", una conexión WebSocket se cae inmediatamente después de conectar, sin ningún error obvio del lado del cliente — parece que "a veces funciona y a veces no".
SSL: esto arma el archivo, no el certificado
El bloque SSL generado apunta a las rutas donde Certbot deja los certificados por defecto (/etc/letsencrypt/live/tu-dominio/). Este generador no obtiene el certificado por vos — corré certbot certonly (o certbot --nginx) por separado antes de recargar Nginx con esta config.
Rate limiting, auth básica y headers de seguridad
limit_req_zone: por qué el burst importa tanto como el rate
limit_req_zone define un balde de tokens compartido (por IP, gracias a $binary_remote_addr) que se rellena a una tasa fija — por ejemplo rate=10r/s significa "10 requests por segundo en promedio". El detalle importante es burst: sin él (o en 0), Nginx rechaza con 503 cualquier ráfaga que supere esa tasa exacta, incluso si es un usuario real haciendo un doble clic o cargando una página con varios assets al mismo tiempo. burst=20 le da margen para absorber esas ráfagas legítimas antes de empezar a bloquear, y nodelay hace que esas requests dentro del burst se procesen inmediatamente en vez de encolarse con demora.
Auth básica, headers de seguridad y páginas de error: cuándo usarlos
auth_basic es ideal para proteger paneles internos (Grafana, phpMyAdmin, un admin sin login propio) con una capa extra sin tocar la app — pero viaja en texto plano codificado, así que solo tiene sentido con SSL habilitado. Los headers de seguridad (X-Frame-Options, X-Content-Type-Options, Referrer-Policy y, con SSL, Strict-Transport-Security) son gratis en costo de performance y cierran vectores comunes de clickjacking y MIME-sniffing. Las páginas de error personalizadas evitan que un usuario vea la página default de Nginx (o peor, un stack trace) cuando algo falla.
Sitios estáticos, load balancing y PHP-FPM: los otros tres modos
SPA vs sitio estático plano: la diferencia está en el try_files
Una SPA (React, Vue, o un export estático de Next.js) maneja su propio ruteo del lado del cliente con JavaScript — si alguien entra directo a /perfil y Nginx busca un archivo perfil.html que no existe, sin fallback te devuelve un 404 real. try_files $uri $uri/ /index.html soluciona esto: si el archivo no existe, sirve index.html igual y deja que el router de JS decida qué mostrar. Un sitio estático plano (HTML generado, sin router de cliente) en cambio debe usar =404 real, para que las URLs rotas efectivamente devuelvan 404 en vez de mostrar la home.
Load balancing: round-robin, least_conn e ip_hash
Sin ningún método explícito, Nginx reparte requests en round-robin (uno para cada backend por turno, ponderado si usás weight). least_conn en cambio manda cada request nueva al backend con menos conexiones activas en ese momento — mejor cuando tus requests tardan tiempos muy distintos entre sí. ip_hash es el que te da "session stickiness": la misma IP de cliente siempre cae en el mismo backend, útil si tu app guarda sesiones en memoria local en vez de en un store compartido (Redis, base de datos) — sin ip_hash, un usuario podría loguearse en un backend y en el siguiente request caer en otro que no lo conoce.
PHP-FPM: los sockets varían por versión de PHP
El socket de PHP-FPM (/run/php/php8.3-fpm.sock en el ejemplo) incluye la versión de PHP en el nombre del archivo, y esa versión depende de qué instalaste — php8.1-fpm, php8.2-fpm, php8.3-fpm son paquetes distintos que corren en paralelo si están instalados. Si tu fastcgi_pass apunta a un socket que no existe, vas a ver un 502 Bad Gateway. Para saber cuál tenés disponible, corré ls /run/php/ en el servidor y vas a ver el .sock real; también podés confirmar el servicio corriendo con systemctl status "php*-fpm".
Preguntas frecuentes
¿Dónde pongo este archivo?+
Guardalo en /etc/nginx/sites-available/tu-dominio.conf, creá un symlink en /etc/nginx/sites-enabled/ apuntando a ese archivo, corré nginx -t para validar la sintaxis, y si no da error, recargá con systemctl reload nginx (o service nginx reload).
¿Esto me consigue el certificado SSL?+
No — solo genera el archivo de configuración que ya asume dónde Certbot/Let's Encrypt van a dejar el certificado. Correr certbot (certbot certonly --nginx -d tu-dominio, o certbot --nginx si querés que lo configure todo solo) sigue siendo un paso aparte, antes de recargar Nginx con esta config.
¿Funciona para apps de Node.js o Next.js?+
Sí — es exactamente el patrón estándar para poner Nginx delante de una app de Node/Next.js/Express corriendo en un puerto local (ej. next start en el puerto 3000): apuntá el "backend upstream" a 127.0.0.1:3000 y listo. Si tu app usa WebSocket (Next.js con HMR, Socket.IO, etc.), activá el toggle de WebSocket.
El rate limiting, ¿va a bloquear usuarios reales?+
No si dejás un burst razonable (20 es un buen default para tráfico humano normal). El burst absorbe ráfagas cortas y legítimas — varias imágenes cargando a la vez, un doble clic — sin devolver 503. Solo empieza a bloquear cuando el volumen sostenido supera claramente la tasa configurada, que es exactamente el comportamiento de un bot o un ataque.
¿Qué modo elijo para una SPA de React o Vue?+
El modo "Sitio estático" con el toggle de SPA fallback activado. Eso genera try_files $uri $uri/ /index.html, que es lo que necesitás para que el router del lado del cliente maneje rutas como /perfil o /dashboard sin que Nginx te devuelva un 404 al entrar directo a esa URL.
¿Qué pasa si un backend del load balancer se cae?+
Nginx detecta los fallos de conexión automáticamente (por defecto, tras max_fails=1 intento fallido lo marca como no disponible por fail_timeout=10s) y deja de mandarle tráfico hasta que vuelva a responder — no necesitás configurar nada extra para ese comportamiento básico. Para health checks activos (chequear un endpoint aunque no haya tráfico real) hace falta el módulo comercial Nginx Plus; con Nginx open source, el chequeo es siempre pasivo, basado en fallos reales.
¿Cómo sé qué socket de PHP-FPM usar?+
Corré ls /run/php/ en tu servidor — vas a ver algo como php8.3-fpm.sock (el número de versión depende de qué paquete php-fpm instalaste). Usá exactamente ese nombre en el campo del socket; si no coincide, Nginx te va a devolver 502 Bad Gateway.
¿Necesitás un VPS para correr esto?
VPS con NVMe real e IP dedicada, ideal para poner Nginx delante de tu API, tu app de Node/Next.js, tu sitio estático o tu instalación de WordPress.
Ver planes