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ísticaNginxHAProxyTraefikCaddy
Soporte de protocolosHTTP, HTTPS, TCP, UDPHTTP, HTTPS, TCPHTTP, HTTPS, TCP, gRPCHTTP, HTTPS
Algoritmos de balanceoRR, LC, IP hash, randomRR, LC, source, URIRR, WRRRR, LC
Health checks activosSolo Nginx PlusSí (nativo)Sí (nativo)Sí (nativo)
SSL automático (ACME)Vía CertbotVía CertbotNativoNativo
Caché integradaNoNoNo
HTTP/31.25+ (experimental)NoNo
Mejor paraWeb + proxy + cachéLB TCP/HTTP puroIngress en KubernetesSSL 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-Proto y Host en cada location de proxy
  • Usa certbot --nginx para aprovisionar certificados SSL sin tiempo de inactividad y con renovación automática
  • Elige least_conn para APIs, ip_hash para persistencia de sesión y random two least_conn para grupos grandes de backends
  • Marca los backends con max_fails y fail_timeout para que Nginx retire los servidores no disponibles automáticamente
  • Usa proxy_http_version 1.1 con las cabeceras Upgrade/Connection para proxy de WebSockets
  • Habilita proxy_cache para endpoints públicos y limit_req_zone para proteger las rutas de autenticación
  • Activa http2 en la directiva listen para multiplexado; evalúa quic en Nginx 1.25+

Artículos Relacionados