Configuración de Proxy Inverso con Nginx: Dirigiendo el Tráfico de Manera Eficiente

Configura un proxy inverso con Nginx con enrutamiento claro, cabeceras correctas, soporte para WebSocket, tiempos de espera, almacenamiento en búfer y pasos para solucionar problemas.

Configuración de Proxy Inverso con Nginx: Dirigiendo el Tráfico de Manera Eficiente

Una configuración de proxy inverso con Nginx permite que Nginx reciba tráfico web público y lo reenvíe a una o más aplicaciones backend. Esto es útil cuando tu aplicación se ejecuta en Node.js, Python, Go, Java u otro servicio que no debería estar expuesto directamente a Internet.

En lugar de que los usuarios se conecten al puerto de tu aplicación, se conectan a Nginx en los puertos HTTP o HTTPS estándar. Nginx maneja el borde público y luego dirige el tráfico de manera eficiente al servicio interno correcto.

Qué Hace un Proxy Inverso

Un proxy inverso se sitúa frente a tus servidores de aplicación. El cliente se comunica con Nginx, y Nginx se comunica con el backend. Para el navegador, Nginx es el sitio web. Para la aplicación, Nginx es el cliente upstream a menos que pases cabeceras que preserven los detalles originales de la solicitud.

Este patrón te ofrece varios beneficios:

  • Puedes ejecutar aplicaciones en puertos privados como 3000, 5000 o 8080.
  • Puedes terminar TLS en Nginx.
  • Puedes enrutar diferentes nombres de host o rutas a diferentes servicios.
  • Puedes agregar almacenamiento en búfer, tiempos de espera, compresión y almacenamiento en caché.
  • Puedes ocultar los detalles de implementación del backend de la red pública.

Un proxy inverso básico para una aplicación que se ejecuta en 127.0.0.1:3000 se ve así:

server {
    listen 80;
    server_name example.com;

    location / {
        proxy_pass http://127.0.0.1:3000;
        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;
    }
}

La directiva proxy_pass le dice a Nginx a dónde enviar la solicitud. Las líneas proxy_set_header preservan el contexto útil de la solicitud. Sin ellas, tu aplicación puede registrar cada solicitud como proveniente de Nginx y puede no saber si la solicitud original usó HTTP o HTTPS.

Si eres nuevo en la estructura de hosts virtuales, revisa bloques de servidor de Nginx antes de dividir el tráfico entre múltiples dominios.

Enrutamiento del Tráfico por Host o Ruta

Las reglas de proxy inverso generalmente enrutan por nombre de host, ruta o ambos. El enrutamiento basado en host es común cuando aplicaciones separadas usan diferentes dominios:

server {
    listen 80;
    server_name api.example.com;

    location / {
        proxy_pass http://127.0.0.1:4000;
        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;
    }
}

El enrutamiento basado en ruta es útil cuando un dominio sirve a múltiples servicios:

server {
    listen 80;
    server_name example.com;

    location /api/ {
        proxy_pass http://127.0.0.1:4000/;
    }

    location / {
        proxy_pass http://127.0.0.1:3000;
    }
}

Ten cuidado con las barras inclinadas al final en proxy_pass. En Nginx, proxy_pass http://backend; y proxy_pass http://backend/; pueden reescribir la URI reenviada de manera diferente cuando se usan dentro de un bloque de ubicación. Prueba las rutas URL exactas que tu aplicación espera.

Por ejemplo, si /api/users llega inesperadamente a tu backend como /users o /api/api/users, verifica primero la combinación del prefijo de ubicación y la barra inclinada final. Ese es uno de los errores más comunes de proxy inverso.

Cabeceras, Tiempos de Espera y WebSockets

Las cabeceras hacen que el backend conozca la solicitud original. La cabecera Host es importante cuando la aplicación construye URLs absolutas, valida hosts permitidos o soporta múltiples inquilinos. X-Forwarded-For ayuda a preservar la IP del cliente original. X-Forwarded-Proto ayuda a las aplicaciones a generar enlaces seguros después de la terminación TLS.

Si tu backend usa WebSockets, agrega cabeceras de actualización:

location /socket/ {
    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;
}

Los tiempos de espera deben coincidir con el comportamiento de tu aplicación. Una solicitud web normal debería terminar rápidamente. Una exportación de informes, un endpoint de transmisión o una solicitud de sondeo largo pueden necesitar más tiempo:

proxy_connect_timeout 5s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;

Evita establecer tiempos de espera enormes en todas partes solo para ocultar un endpoint lento. Los tiempos de espera largos pueden acaparar recursos y hacer que las fallas reales sean más difíciles de notar. Ajusta la ubicación que lo necesita.

El almacenamiento en búfer es otra configuración importante. Por defecto, Nginx puede almacenar en búfer las respuestas upstream antes de enviarlas al cliente. Esto es útil para muchas aplicaciones web, pero los endpoints de transmisión pueden necesitar que el almacenamiento en búfer esté deshabilitado:

proxy_buffering off;

Úsalo solo donde se requiera comportamiento de transmisión. Para respuestas HTML y API estándar, el almacenamiento en búfer a menudo mejora la estabilidad.

Terminación TLS y Redirecciones HTTPS

En muchas configuraciones, Nginx también maneja HTTPS. Esto permite que la aplicación backend se ejecute en un puerto HTTP privado mientras los usuarios obtienen un sitio seguro normal en el puerto 443.

Una forma común se ve así:

server {
    listen 80;
    server_name example.com;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl;
    server_name example.com;

    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

    location / {
        proxy_pass http://127.0.0.1:3000;
        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;
    }
}

El bloque del servidor de redirección es intencionalmente pequeño. Hace un trabajo: mover el tráfico HTTP simple a HTTPS. El bloque del servidor HTTPS maneja el proxy.

Si tu aplicación está detrás de Nginx y aún genera enlaces http://, verifica si confía en X-Forwarded-Proto. Muchos frameworks necesitan una configuración como "trust proxy" o una lista de proxies permitidos antes de usar las cabeceras reenviadas. No confíes ciegamente en las cabeceras reenviadas de Internet público en la capa de aplicación; asegúrate de que solo Nginx pueda alcanzar el puerto de la aplicación.

Grupos Upstream y Balanceo de Carga Simple

Cuando un backend no es suficiente, define un grupo upstream:

upstream app_backend {
    server 10.0.1.10:3000;
    server 10.0.1.11:3000;
    keepalive 32;
}

server {
    listen 80;
    server_name example.com;

    location / {
        proxy_pass http://app_backend;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        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;
    }
}

Nginx de código abierto usa balanceo de carga round-robin por defecto. También puedes usar opciones como least_conn cuando las solicitudes largas hacen que un backend esté más ocupado que los demás. La verificación de salud en Nginx de código abierto es principalmente pasiva: si un backend falla, Nginx puede marcarlo como no disponible por un período basado en configuraciones de falla. Nginx Plus tiene verificaciones de salud activas, pero no asumas que esas características existen en todas las instalaciones.

Keepalive en el bloque upstream mantiene las conexiones backend abiertas para su reutilización. Eso ayuda con muchas solicitudes pequeñas, pero el backend debe ser capaz de manejar el número de conexiones inactivas y activas que Nginx pueda mantener.

Contenedores y Redes Privadas

La configuración del proxy inverso a menudo se vuelve confusa en Docker o Kubernetes porque localhost cambia de significado. Si Nginx se ejecuta dentro de un contenedor, 127.0.0.1:3000 apunta al propio contenedor de Nginx, no a un contenedor de aplicación separado.

En Docker Compose, haz proxy al nombre del servicio:

location / {
    proxy_pass http://app:3000;
}

En Kubernetes, generalmente haces proxy a un nombre DNS de Servicio, aunque muchas implementaciones de Kubernetes usan un controlador Ingress en lugar de bloques de servidor Nginx escritos a mano.

La regla simple es esta: prueba la conectividad desde donde se ejecuta Nginx, no desde tu computadora portátil ni desde el contenedor backend. Si esto falla, Nginx también fallará:

curl -v http://app:3000/

Ejecuta eso dentro del contenedor de Nginx o en el host de Nginx, dependiendo de tu implementación.

Límites de Seguridad que Vale la Pena Revisar

Un proxy inverso debería reducir la exposición pública, no crearla accidentalmente. La aplicación backend normalmente debería escuchar en una interfaz privada, una subred privada o una red de contenedores. Si tu aplicación escucha en 0.0.0.0:3000 en una VM pública, los usuarios pueden eludir Nginx por completo visitando http://example.com:3000.

Verifica los puertos de escucha en el host:

sudo ss -ltnp

Si el backend debe escuchar en todas las interfaces dentro de un contenedor, usa reglas de firewall, grupos de seguridad o configuraciones de red del contenedor para que solo Nginx pueda alcanzarlo desde el exterior. Esto es importante porque las aplicaciones a menudo dependen de Nginx para TLS, límites de tamaño de solicitud, límites de velocidad, puertas de enlace de autenticación o listas de permitidos de IP.

También ten cuidado con las cabeceras reenviadas. Cabeceras como X-Forwarded-For son fáciles de falsificar para los clientes a menos que Nginx las sobrescriba y la aplicación confíe solo en el proxy. El patrón común de Nginx es:

proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

Eso agrega la dirección del cliente a la cadena. Tu aplicación o pipeline de registro debe saber qué direcciones de proxy son confiables. De lo contrario, la limitación de velocidad o los registros de auditoría pueden registrar la IP "cliente" incorrecta.

Los límites de tamaño de solicitud también pertenecen a esta conversación. Si tu aplicación acepta cargas de archivos, establece client_max_body_size intencionalmente:

client_max_body_size 25m;

No lo eleves globalmente a un valor enorme a menos que cada ruta lo necesite. Un endpoint de carga de foto de perfil y un endpoint de inicio de sesión JSON no deberían necesitar el mismo límite de cuerpo de solicitud.

Una Lista de Verificación Práctica para la Implementación

Antes de dar por terminado el proxy inverso, pruébalo como usuario y como operador:

  • curl -I http://example.com/ debería mostrar la redirección o respuesta esperada.
  • curl -I https://example.com/ debería mostrar el estado y las cabeceras esperados.
  • Los registros de la aplicación deberían mostrar el host original y una IP de cliente útil.
  • Los endpoints WebSocket o de transmisión deben probarse por separado.
  • Una ruta incorrecta, como /api/does-not-exist, debería fallar de la manera que tu aplicación espera.
  • Los registros de error de Nginx deberían estar silenciosos durante las solicitudes normales.

Para el enrutamiento de rutas, me gusta probar tres URLs para cada ubicación: el prefijo desnudo, una ruta anidada normal y una ruta con una cadena de consulta. Por ejemplo:

curl -i http://example.com/api/
curl -i http://example.com/api/users
curl -i 'http://example.com/api/users?page=2'

Esas comprobaciones simples detectan muchos errores de barra inclinada final antes que los usuarios.

Cuando recargues, usa la misma secuencia segura cada vez:

sudo nginx -t
sudo systemctl reload nginx
sudo tail -n 50 /var/log/nginx/error.log

Si la aplicación está detrás de la terminación TLS, también verifica que los enlaces generados, las redirecciones, las cookies y las URLs de devolución de llamada usen HTTPS. Los flujos de inicio de sesión son donde esto a menudo se rompe primero, porque las redirecciones y las cookies seguras dependen de que la aplicación entienda el esquema original.

Patrones de Falla Comunes

502 Bad Gateway generalmente significa que Nginx alcanzó la ubicación del proxy inverso pero no pudo obtener una respuesta válida del upstream. El backend puede estar caído, el puerto puede ser incorrecto, la aplicación puede estar escuchando en una interfaz diferente, o la conexión puede ser rechazada por un firewall.

504 Gateway Timeout generalmente significa que Nginx se conectó a algo pero no recibió una respuesta a tiempo. Eso puede ser una aplicación lenta, una consulta de base de datos bloqueada, un grupo de trabajadores sobrecargado o un tiempo de espera demasiado corto para el endpoint. Aumentar proxy_read_timeout puede ser apropiado para un endpoint de exportación de larga duración conocido. No es una solución para una aplicación generalmente lenta.

Los bucles de redirección a menudo provienen de un desajuste entre la terminación TLS y la configuración de confianza de la aplicación. El navegador llega a Nginx a través de HTTPS, Nginx hace proxy a la aplicación a través de HTTP, y la aplicación piensa que la solicitud original era HTTP simple. La aplicación redirige a HTTPS, pero sucede lo mismo nuevamente. Pasar X-Forwarded-Proto es solo la mitad de la solución; la aplicación también debe confiar en él desde el proxy.

Las IPs de cliente faltantes generalmente se manifiestan como cada solicitud proveniente de 127.0.0.1, una dirección de puente Docker o una dirección de balanceador de carga privada. Pasa X-Real-IP y X-Forwarded-For, luego configura la aplicación y la capa de registro para leerlas de manera segura.

Los activos estáticos rotos después del enrutamiento de rutas a menudo provienen de aplicaciones que asumen que viven en /. Si montas una aplicación bajo /admin/, aún puede generar enlaces a /assets/app.css. A veces puedes solucionar esto con configuraciones de ruta base de la aplicación. Intentar reescribir cada ruta de activo en Nginx generalmente es frágil.

Un Pequeño Ejemplo del Mundo Real

Imagina una VM ejecutando tres servicios:

  • Un sitio de marketing en 127.0.0.1:3000
  • Una API en 127.0.0.1:4000
  • Una herramienta de administración en 127.0.0.1:5000

Podrías enrutarlos así:

server {
    listen 443 ssl;
    server_name example.com;

    location /api/ {
        proxy_pass http://127.0.0.1:4000/;
        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;
    }

    location /admin/ {
        proxy_pass http://127.0.0.1:5000/;
        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;
    }

    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;
    }
}

Esto puede funcionar, pero tiene desventajas. La API y la aplicación de administración deben comportarse correctamente bajo sus prefijos. Si no lo hacen, nombres de host separados como api.example.com y admin.example.com pueden ser más limpios. Un buen diseño de proxy inverso no se trata solo de hacer que Nginx acepte la configuración; se trata de elegir un enrutamiento con el que tus aplicaciones puedan vivir.

Pruebas y Solución de Problemas de la Configuración

Siempre prueba la configuración antes de recargar:

nginx -t

Luego recarga Nginx y haz una solicitud a través del nombre de host público. Verifica tanto el navegador como los registros. Los registros de acceso de Nginx muestran si la solicitud llegó a Nginx. Los registros de error muestran fallas de conexión, tiempos de espera upstream y detalles de bad gateway.

Un ejemplo práctico: tu aplicación Node.js funciona bien con curl http://127.0.0.1:3000, pero el sitio público muestra 502 Bad Gateway. Eso significa que Nginx es accesible, pero no puede comunicarse con el upstream. Verifica si la aplicación está escuchando en la dirección esperada, si el puerto es correcto y si un firewall local bloquea la conexión.

Los problemas comunes de proxy inverso incluyen:

  • Puerto o dirección upstream incorrectos.
  • Backend vinculado a localhost cuando Nginx se ejecuta en otro contenedor.
  • Cabeceras de actualización WebSocket faltantes.
  • Aplicación rechazando solicitudes porque la cabecera Host es inesperada.
  • Reescribir URI incorrecta causada por una barra inclinada final.
  • Tiempos de espera demasiado cortos para un endpoint lento.

Para fallas upstream más profundas, usa solución de problemas de Nginx 502.

Cuándo Buscar Ayuda

Pide ayuda a un ingeniero DevOps si el proxy inverso abarca múltiples contenedores, redes privadas, certificados TLS o upstreams con balanceo de carga. Estas configuraciones pueden fallar de maneras que parecen problemas de Nginx pero que en realidad son problemas de DNS, firewall, redes de contenedores o salud de la aplicación.

También debes buscar ayuda antes de exponer paneles de administración, APIs internas o servicios de staging a través de un proxy inverso público. Pequeños errores de enrutamiento pueden crear problemas de acceso graves.

Una configuración de proxy inverso con Nginx es uno de los patrones más útiles en la infraestructura web. Mantén el enrutamiento claro, pasa las cabeceras correctas, prueba el comportamiento de las rutas cuidadosamente y deja que Nginx sea el punto de entrada público estable para tus servicios backend.