TL;DR — Resumen Rápido
Configura Nginx como proxy inverso y balanceador de carga con SSL. Cubre proxy_pass, Let's Encrypt, algoritmos de balanceo, WebSocket, caché y rate limiting.
Nginx impulsa más del 34% de los sitios web más visitados del mundo — y una gran parte de ese tráfico fluye a través de él no como servidor web sino como proxy inverso y balanceador de carga. Configurar Nginx como proxy inverso con terminación SSL ofrece un único punto de entrada reforzado que distribuye la carga entre servidores backend, descarga TLS de tu aplicación y agrega caché, límite de peticiones y cabeceras de seguridad en un solo lugar. Esta guía cubre cada capa de ese esquema: fundamentos del proxy inverso, terminación SSL con Let’s Encrypt, los cuatro algoritmos de balanceo de carga, WebSocket, caché de respuestas, limitación de peticiones, cabeceras de seguridad, HTTP/2 y HTTP/3, monitorización y un escenario real con un clúster de Node.js.
Prerrequisitos
Antes de comenzar, asegúrate de tener:
- Ubuntu 22.04 o 24.04 (los comandos aplican a cualquier distro basada en Debian)
- Nginx 1.18 o superior (
sudo apt install nginx) - Un dominio registrado apuntando a la IP pública de tu servidor
- Acceso a la terminal con privilegios sudo
- Al menos dos servidores o procesos de aplicación backend (para el balanceo de carga)
Fundamentos del Proxy Inverso
Un proxy inverso se sitúa entre internet y uno o más servidores backend. Los clientes se conectan a Nginx; Nginx reenvía la petición al backend correspondiente y devuelve la respuesta. El backend nunca necesita una IP pública.
La directiva central es proxy_pass:
server {
listen 80;
server_name app.ejemplo.com;
location / {
proxy_pass http://127.0.0.1:3000;
# Reenvía la IP real del cliente al backend
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# Indica al backend qué host solicitó el cliente
proxy_set_header Host $host;
# Indica al backend si el cliente usó HTTP o HTTPS
proxy_set_header X-Forwarded-Proto $scheme;
# Preserva la IP real en el nivel del proxy más externo
proxy_set_header X-Real-IP $remote_addr;
}
}
Las cuatro directivas proxy_set_header son indispensables en cualquier configuración de producción. Sin X-Forwarded-For, los logs de tu aplicación solo muestran la IP del servidor Nginx. Sin X-Forwarded-Proto, tu app no puede distinguir HTTP de HTTPS y puede generar bucles de redirección incorrectos.
Define tiempos de espera razonables para que los backends lentos no bloqueen los workers de Nginx:
proxy_connect_timeout 10s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
proxy_buffering on;
proxy_buffer_size 8k;
proxy_buffers 8 16k;
Terminación SSL con Let’s Encrypt
La terminación SSL significa que Nginx gestiona el handshake TLS y reenvía HTTP plano a los backends en la red interna — sin sobrecarga TLS en los servidores de aplicación individuales.
Instala Certbot con el plugin para Nginx:
sudo apt install certbot python3-certbot-nginx -y
Obtén e instala un certificado (Certbot modifica tu bloque server automáticamente):
sudo certbot --nginx -d app.ejemplo.com -d www.app.ejemplo.com
Certbot crea un temporizador systemd que renueva los certificados automáticamente antes de que expiren. Verifica que funciona:
sudo systemctl status certbot.timer
sudo certbot renew --dry-run
Algoritmos de Balanceo de Carga
Para balancear la carga entre varios backends, sustituye el destino único de proxy_pass por un bloque upstream:
upstream app_cluster {
server 10.0.0.10:3000;
server 10.0.0.11:3000;
server 10.0.0.12:3000;
}
Round-Robin (Predeterminado)
No se necesita ninguna directiva. Nginx distribuye las peticiones secuencialmente entre todos los servidores upstream. Añade pesos para dirigir más tráfico a nodos más potentes:
upstream app_cluster {
server 10.0.0.10:3000 weight=3;
server 10.0.0.11:3000 weight=1;
server 10.0.0.12:3000 weight=1;
}
Menos Conexiones (least_conn)
Nginx enruta cada nueva petición al servidor upstream con menos conexiones activas. Ideal para cargas de trabajo con tiempos de respuesta variables:
upstream app_cluster {
least_conn;
server 10.0.0.10:3000;
server 10.0.0.11:3000;
server 10.0.0.12:3000;
}
IP Hash (ip_hash)
La IP del cliente determina qué backend sirve todas las peticiones de ese cliente. Útil para persistencia de sesión sin un almacén de sesión compartido:
upstream app_cluster {
ip_hash;
server 10.0.0.10:3000;
server 10.0.0.11:3000;
server 10.0.0.12:3000;
}
Aleatorio (random)
Nginx selecciona un backend aleatoriamente. Con el parámetro two, elige dos servidores al azar y enruta al que tiene menos conexiones:
upstream app_cluster {
random two least_conn;
server 10.0.0.10:3000;
server 10.0.0.11:3000;
server 10.0.0.12:3000;
}
Comprobaciones de Salud de Upstream
Marca servidores como backup o establece umbrales de fallo para que Nginx deje de enviar tráfico a backends no disponibles:
upstream app_cluster {
least_conn;
server 10.0.0.10:3000 max_fails=3 fail_timeout=30s;
server 10.0.0.11:3000 max_fails=3 fail_timeout=30s;
server 10.0.0.12:3000 backup;
}
Proxy de WebSockets
Las conexiones WebSocket requieren un handshake de upgrade HTTP/1.1. Añade tres directivas a cualquier bloque location que haga proxy de tráfico WebSocket:
location /ws/ {
proxy_pass http://app_cluster;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 3600s;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
Caché con proxy_cache
Nginx puede almacenar en caché las respuestas de los backends en memoria o en disco, reduciendo drásticamente la carga del backend en endpoints cacheables.
Define una zona de caché en el contexto http:
http {
proxy_cache_path /var/cache/nginx
levels=1:2
keys_zone=app_cache:10m
max_size=1g
inactive=60m
use_temp_path=off;
}
Habilita la caché en el bloque location:
location /api/public/ {
proxy_pass http://app_cluster;
proxy_cache app_cache;
proxy_cache_valid 200 10m;
proxy_cache_valid 404 1m;
proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
add_header X-Cache-Status $upstream_cache_status;
}
Límite de Peticiones
La limitación de peticiones protege los backends de picos de tráfico y ataques de fuerza bruta. Define una zona de memoria compartida en el contexto http:
http {
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=30r/m;
}
Aplica la zona a una ubicación:
location /api/ {
limit_req zone=api_limit burst=10 nodelay;
limit_req_status 429;
proxy_pass http://app_cluster;
}
Cabeceras de Seguridad
Añade cabeceras de seguridad en el bloque server:
server {
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-XSS-Protection "1; mode=block" 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;
add_header Strict-Transport-Security "max-age=15768000; includeSubDomains" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline';" always;
server_tokens off;
}
HTTP/2 y HTTP/3
Activa HTTP/2 en la directiva listen:
listen 443 ssl http2;
Para HTTP/3 (Nginx 1.25+):
listen 443 ssl http2;
listen 443 quic reuseport;
add_header Alt-Svc 'h3=":443"; ma=86400' always;
Monitorización con stub_status
Activa el módulo stub_status para exponer métricas de conexión en tiempo real:
server {
listen 127.0.0.1:8080;
location /nginx_status {
stub_status;
allow 127.0.0.1;
deny all;
}
}
curl http://127.0.0.1:8080/nginx_status
Comparativa: Nginx vs. Alternativas
| Característica | Nginx | HAProxy | Traefik | Caddy |
|---|---|---|---|---|
| Soporte de protocolos | HTTP, HTTPS, TCP, UDP | HTTP, HTTPS, TCP | HTTP, HTTPS, TCP, gRPC | HTTP, HTTPS |
| Algoritmos de balanceo | RR, LC, IP hash, random | RR, LC, source, URI | RR, WRR | RR, LC |
| Health checks activos | Solo Nginx Plus | Sí (nativo) | Sí (nativo) | Sí (nativo) |
| SSL automático (ACME) | Vía Certbot | Vía Certbot | Nativo | Nativo |
| Caché integrada | Sí | No | No | No |
| HTTP/3 | 1.25+ (experimental) | No | No | Sí |
| Mejor para | Web + proxy + caché | LB TCP/HTTP puro | Ingress en Kubernetes | SSL automático simple |
Escenario Real: Balanceo de Carga de un Clúster Node.js con SSL
Tienes una API Node.js de producción ejecutando tres procesos PM2 en el mismo host (puertos 3001, 3002, 3003) y quieres exponerlos como api.ejemplo.com sobre HTTPS con afinidad de sesión, limitación de peticiones en el endpoint de autenticación y soporte WebSocket.
upstream node_api {
ip_hash;
server 127.0.0.1:3001;
server 127.0.0.1:3002;
server 127.0.0.1:3003;
}
limit_req_zone $binary_remote_addr zone=auth_limit:5m rate=5r/m;
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=60r/m;
server {
listen 443 ssl http2;
server_name api.ejemplo.com;
ssl_certificate /etc/letsencrypt/live/api.ejemplo.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/api.ejemplo.com/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
add_header Strict-Transport-Security "max-age=15768000; includeSubDomains" always;
add_header X-Content-Type-Options "nosniff" always;
server_tokens off;
location /api/ {
limit_req zone=api_limit burst=20 nodelay;
limit_req_status 429;
proxy_pass http://node_api;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /api/auth/ {
limit_req zone=auth_limit burst=3 nodelay;
limit_req_status 429;
proxy_pass http://node_api;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /ws/ {
proxy_pass http://node_api;
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-Forwarded-For $proxy_add_x_forwarded_for;
proxy_read_timeout 3600s;
}
}
server {
listen 80;
server_name api.ejemplo.com;
return 301 https://$host$request_uri;
}
Errores Comunes y Casos Límite
La barra final en proxy_pass importa. proxy_pass http://backend; conserva el URI completo incluyendo el prefijo /api/. proxy_pass http://backend/; elimina el prefijo del location y puede causar errores 404 difíciles de diagnosticar.
No uses ip_hash si tienes una CDN delante de Nginx. Todo el tráfico de la CDN parece provenir de un pequeño conjunto de IPs, por lo que ip_hash enviará todo ese tráfico a un solo backend.
Las conexiones largas y SSE requieren tiempos de espera aumentados. Las conexiones Server-Sent Events y long-polling permanecen abiertas indefinidamente. Ajusta proxy_read_timeout al intervalo de heartbeat de tu aplicación más un margen.
proxy_buffering y streaming. Si tu backend transmite una respuesta (SSE, transferencia por chunks), establece proxy_buffering off en esa location. De lo contrario Nginx almacena en búfer la respuesta completa antes de enviarla, anulando el propósito del streaming.
Solución de Problemas
502 Bad Gateway. El backend no está en ejecución o no escucha en el puerto configurado. Comprueba: sudo systemctl status tu-app, ss -tlnp | grep 3000.
504 Gateway Timeout. El backend responde demasiado lento. Aumenta proxy_read_timeout o investiga el rendimiento del backend.
Las conexiones WebSocket caen después de 60 segundos. El proxy_read_timeout predeterminado de Nginx es 60 segundos. Auméntalo en los locations de WebSocket.
La caché no funciona. Asegúrate de que proxy_cache_path está definido en el contexto http, que el directorio existe y tiene permisos de escritura para el usuario nginx, y que proxy_cache está configurado en el bloque location correcto.
Resumen
Nginx como proxy inverso y balanceador de carga ofrece una capa de tráfico de nivel producción sin infraestructura adicional. Puntos clave:
- Establece siempre
X-Forwarded-For,X-Forwarded-ProtoyHosten cada location de proxy - Usa
certbot --nginxpara aprovisionar certificados SSL sin tiempo de inactividad y con renovación automática - Elige
least_connpara APIs,ip_hashpara persistencia de sesión yrandom two least_connpara grupos grandes de backends - Marca los backends con
max_failsyfail_timeoutpara que Nginx retire los servidores no disponibles automáticamente - Usa
proxy_http_version 1.1con las cabeceras Upgrade/Connection para proxy de WebSockets - Habilita
proxy_cachepara endpoints públicos ylimit_req_zonepara proteger las rutas de autenticación - Activa
http2en la directiva listen para multiplexado; evalúaquicen Nginx 1.25+
Artículos Relacionados
- Cómo: Compilar tu propia versión de NginX
- Fail2Ban: Protege tu servidor Linux de ataques de fuerza bruta
- Cómo configurar el firewall UFW en Ubuntu Server: Guía completa
- Cómo pasar a Nginx la IP correcta del cliente usando un proxy inverso Varnish
- Lista de verificación de seguridad para servidores Linux: 20 pasos esenciales