Cómo configurar Nginx como reverse proxy

12 min de lectura·Matthieu|

Configura Nginx como reverse proxy para Node.js, Ollama y otros backends. Cubre proxy_pass, reenvío de headers, WebSocket, upstream load balancing y ajuste de timeouts en producción con verificación en cada paso.

Un reverse proxy se sitúa entre los clientes y tu aplicación backend. Nginx recibe las peticiones entrantes, las reenvía a un servidor backend (Node.js, Python, Go, Ollama) y devuelve la respuesta al cliente. Esto permite añadir terminación TLS, load balancing, caché y control de acceso sin modificar tu aplicación.

Este tutorial cubre la configuración de Nginx como reverse proxy desde un proxy_pass básico hasta WebSocket proxying, upstream load balancing y una configuración de Ollama lista para producción.

Requisitos previos

Antes de empezar, necesitas:

Verifica que Nginx está corriendo:

sudo systemctl status nginx

Deberías ver active (running) en la salida.

¿Cómo se configura proxy_pass en Nginx?

La directiva proxy_pass indica a Nginx dónde reenviar las peticiones. Se coloca dentro de un bloque location dentro de un bloque server. Nginx envía la petición del cliente a la URL del backend especificada y devuelve la respuesta. La directiva acepta URLs HTTP y HTTPS, y puede apuntar a una dirección IP, nombre de dominio o socket Unix.

Crea un nuevo archivo de server block:

sudo nano /etc/nginx/sites-available/app.conf

Añade una configuración mínima de reverse proxy:

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

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

Activa el sitio y prueba la configuración:

sudo ln -s /etc/nginx/sites-available/app.conf /etc/nginx/sites-enabled/
sudo nginx -t

Si nginx -t devuelve syntax is ok y test is successful, recarga:

sudo systemctl reload nginx

Verifica que el proxy funciona:

curl -I http://app.example.com

Las cabeceras de respuesta deben provenir de tu aplicación backend. Si ves un 502 Bad Gateway, tu backend no está corriendo en el puerto 3000.

¿Qué pasa con la barra final en proxy_pass?

La barra final (trailing slash) en proxy_pass controla la reescritura de URIs. Esta es una de las fuentes de errores de configuración más frecuentes.

Configuración Petición Reenviado al backend
proxy_pass http://127.0.0.1:3000 /app/users http://127.0.0.1:3000/app/users
proxy_pass http://127.0.0.1:3000/ /app/users http://127.0.0.1:3000/users
proxy_pass http://127.0.0.1:3000/v2/ /app/users http://127.0.0.1:3000/v2/users
proxy_pass http://127.0.0.1:3000/v2 /app/users http://127.0.0.1:3000/v2users

La regla: cuando proxy_pass incluye un URI (cualquier cosa después de host:port, incluso solo /), Nginx elimina el prefijo del location coincidente de la URI de la petición y añade el resto a la URI de proxy_pass. Cuando proxy_pass no tiene URI, la ruta original se pasa sin cambios.

Para un bloque location /app/, se aplican los ejemplos anteriores. Fíjate en la cuarta fila: sin la barra final en /v2, la ruta se convierte en /v2users en lugar de /v2/users. Incluye siempre la barra final cuando especifiques una ruta.

¿Qué headers debes reenviar al backend?

Sin una configuración explícita de headers, tu aplicación backend no puede ver la IP real del cliente, el protocolo original ni el hostname solicitado. Nginx los reemplaza por defecto. Reenvíalos manualmente.

Header Valor Propósito
Host $host Preserva el header Host original para que el backend sepa qué dominio se solicitó
X-Real-IP $remote_addr Pasa la dirección IP del cliente al backend
X-Forwarded-For $proxy_add_x_forwarded_for Añade la IP del cliente a la cadena de proxies
X-Forwarded-Proto $scheme Indica al backend si la petición original usó HTTP o HTTPS

Añade estos headers a tu bloque location:

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

Después de recargar Nginx, verifica que los headers llegan al backend. Si tu aplicación registra los headers entrantes, compruébalos:

sudo systemctl reload nginx
curl -s http://app.example.com/headers

Tu backend debería ver X-Real-IP con la IP del cliente, no 127.0.0.1. Si muestra 127.0.0.1, las directivas proxy_set_header faltan o están en el contexto incorrecto.

Nota de seguridad: usa $proxy_add_x_forwarded_for en lugar de $remote_addr para X-Forwarded-For. Añade la IP del cliente a cualquier header X-Forwarded-For existente. Si solo usas $remote_addr, pierdes la cadena de proxies, lo que rompe el rastreo de IPs detrás de múltiples proxies. Si Nginx es tu único proxy, ambos son equivalentes.

¿Cómo hacer reverse proxy de una aplicación Node.js?

Este es un server block completo para hacer proxy de una aplicación Node.js corriendo en el puerto 3000. El ejemplo incluye reenvío de headers, soporte WebSocket y ocultación de versión.

Crea el server block:

sudo nano /etc/nginx/sites-available/nodeapp.conf
server {
    listen 80;
    server_name nodeapp.example.com;

    # Hide Nginx version in responses
    server_tokens off;

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;

        # Header forwarding
        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;

        # WebSocket support
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";

        # Timeouts
        proxy_connect_timeout 60s;
        proxy_read_timeout 60s;
        proxy_send_timeout 60s;
    }
}

Activa, prueba y recarga:

sudo ln -s /etc/nginx/sites-available/nodeapp.conf /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx

Verifica que el proxy funciona:

curl -I http://nodeapp.example.com

Fíjate bien: la respuesta no debería incluir una línea Server: nginx/1.x.x. La directiva server_tokens off oculta el número de versión. Exponer la versión ayuda a los atacantes a apuntar a vulnerabilidades conocidas.

Para terminación TLS con Let's Encrypt, consulta Nginx SSL/TLS con Let's Encrypt.

¿Cómo hacer proxy de conexiones WebSocket con Nginx?

Las conexiones WebSocket comienzan como una petición HTTP con un header Upgrade, y luego cambian a una conexión bidireccional persistente. Nginx usa HTTP/1.0 por defecto para conexiones upstream, que no soporta el mecanismo Upgrade. Necesitas tres directivas para que WebSocket funcione: establecer proxy_http_version a 1.1, reenviar el header Upgrade y configurar el header Connection a "upgrade".

Para locations que sirven tanto tráfico HTTP normal como WebSocket, usa la directiva map para configurar el header Connection de forma condicional. Añade esto al bloque http en /etc/nginx/nginx.conf:

map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}

Después referencia $connection_upgrade en tu server block:

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

    location / {
        proxy_pass http://127.0.0.1:8080;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $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;

        # Prevent idle timeout killing WebSocket connections
        proxy_read_timeout 3600s;
        proxy_send_timeout 3600s;
    }
}

El proxy_read_timeout por defecto es 60 segundos. Si no pasan datos por la conexión en esa ventana, Nginx la cierra. Configúralo más alto para conexiones WebSocket. Tu aplicación debería enviar ping frames de WebSocket a un intervalo menor que este timeout.

Prueba la configuración:

sudo nginx -t && sudo systemctl reload nginx

Para verificar la conectividad WebSocket, instala wscat y conéctate:

npm install -g wscat
wscat -c ws://ws.example.com/

Si la conexión se abre, el WebSocket proxying funciona. Si recibes unexpected server response (200), los headers Upgrade no se están reenviando. Verifica que la directiva map está dentro del bloque http, no dentro de un bloque server.

¿Cómo hacer reverse proxy de Ollama para IA autoalojada?

Ollama sirve inferencia LLM en el puerto 11434 por defecto y se vincula a 127.0.0.1. Hacer proxy a través de Nginx permite añadir TLS, autenticación y control de acceso sin modificar la configuración de Ollama. La diferencia principal respecto al proxy estándar: la inferencia LLM puede tardar minutos, y las respuestas en streaming necesitan el buffering desactivado.

Crea el server block:

sudo nano /etc/nginx/sites-available/ollama.conf
server {
    listen 80;
    server_name ollama.example.com;

    server_tokens off;

    # Restrict access to specific IPs
    allow 192.168.1.0/24;
    allow 10.0.0.0/8;
    deny all;

    location / {
        proxy_pass http://127.0.0.1:11434;
        proxy_http_version 1.1;

        # Header forwarding
        proxy_set_header Host localhost:11434;
        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_set_header Connection '';

        # Disable buffering for streaming responses
        proxy_buffering off;
        proxy_cache off;
        chunked_transfer_encoding on;

        # Extended timeouts for LLM inference
        proxy_connect_timeout 300s;
        proxy_read_timeout 600s;
        proxy_send_timeout 600s;
    }
}

Activa y prueba:

sudo ln -s /etc/nginx/sites-available/ollama.conf /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx

Verifica que Ollama responde a través del proxy:

curl http://ollama.example.com/api/tags

Deberías ver una respuesta JSON con la lista de modelos disponibles. Si recibes un 403 Forbidden, la IP de tu cliente no está en la lista allow. Si recibes un 502 Bad Gateway, Ollama no está corriendo:

sudo systemctl status ollama

Prueba una generación en streaming:

curl -N http://ollama.example.com/api/generate -d '{
  "model": "llama3.2",
  "prompt": "Hello",
  "stream": true
}'

Fíjate bien: el flag -N desactiva el buffering de salida de curl. Deberías ver los tokens llegar uno a uno. Si la respuesta completa llega de golpe, proxy_buffering off no está configurado o está siendo sobreescrito.

Por qué estos ajustes difieren de un proxy estándar:

  • proxy_buffering off: Nginx normalmente almacena las respuestas del backend en buffer y las envía como lote. Para streaming LLM, quieres que cada token se envíe al cliente de inmediato.
  • proxy_read_timeout 600s: La inferencia LLM en modelos grandes puede tardar varios minutos. El timeout por defecto de 60s cortaría la conexión a mitad de la generación.
  • proxy_set_header Host localhost:11434: Ollama comprueba el header Host y rechaza peticiones que no coinciden con su dirección de enlace configurada.
  • proxy_set_header Connection '': Limpia el header Connection para evitar problemas de keep-alive con streaming chunked.

Para terminación TLS y exponer Ollama de forma segura en internet, consulta Nginx SSL/TLS con Let's Encrypt. Nunca expongas Ollama sin control de acceso. Combina restricciones por IP con HTTP Basic Auth o validación de API key para uso en producción.

Para añadir HTTP Basic Auth sobre las restricciones de IP:

sudo apt install apache2-utils
sudo htpasswd -c /etc/nginx/.ollama_htpasswd apiuser
sudo chmod 640 /etc/nginx/.ollama_htpasswd
sudo chown root:www-data /etc/nginx/.ollama_htpasswd

Luego añade al bloque location en ollama.conf:

    auth_basic "Ollama API";
    auth_basic_user_file /etc/nginx/.ollama_htpasswd;

Verifica que los permisos del archivo son correctos:

ls -la /etc/nginx/.ollama_htpasswd

El archivo debería mostrar -rw-r----- con propietario root y grupo www-data. Restringir los permisos evita que otros usuarios del servidor lean los hashes de contraseñas.

¿Cómo configurar upstream load balancing?

El bloque upstream define un grupo de servidores backend entre los que Nginx distribuye las peticiones. Por defecto, Nginx usa weighted round-robin. Cada servidor recibe una proporción de peticiones proporcional a su peso (peso por defecto: 1).

Añade un bloque upstream antes de tu bloque server:

upstream app_backends {
    server 127.0.0.1:3000;
    server 127.0.0.1:3001;
    server 127.0.0.1:3002;
}

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

    location / {
        proxy_pass http://app_backends;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

Métodos de load balancing

Round-robin (por defecto): las peticiones se distribuyen equitativamente entre los servidores. No necesita directiva.

Least connections (menos conexiones): envía peticiones al servidor con menos conexiones activas. Mejor para backends con tiempos de respuesta variables:

upstream app_backends {
    least_conn;
    server 127.0.0.1:3000;
    server 127.0.0.1:3001;
}

IP hash: vincula cada IP de cliente a un backend específico. Útil para persistencia de sesión sin cookies sticky:

upstream app_backends {
    ip_hash;
    server 127.0.0.1:3000;
    server 127.0.0.1:3001;
}

Health checks con max_fails y fail_timeout

Nginx monitoriza pasivamente la salud de los backends. Si un servidor no responde, Nginx lo marca como no disponible y deja de enviarle peticiones durante un periodo.

upstream app_backends {
    server 127.0.0.1:3000 max_fails=3 fail_timeout=30s;
    server 127.0.0.1:3001 max_fails=3 fail_timeout=30s;
    server 127.0.0.1:3002 backup;
}
Parámetro Por defecto Descripción
max_fails 1 Número de intentos fallidos dentro de fail_timeout antes de marcar el servidor como no disponible
fail_timeout 10s Ventana para contar fallos, y tiempo que el servidor permanece marcado como no disponible
backup - El servidor solo recibe peticiones cuando todos los servidores primarios están caídos
weight 1 Proporción relativa de peticiones en round-robin

Después de configurar upstream, prueba y recarga:

sudo nginx -t && sudo systemctl reload nginx

Verifica que el load balancing funciona enviando múltiples peticiones y comprobando qué backend responde:

for i in $(seq 1 6); do curl -s http://app.example.com/health; echo; done

Si tus backends devuelven un identificador en su respuesta, deberías ver las peticiones distribuidas entre ellos.

¿Cómo ajustar proxy buffering y timeouts?

Nginx almacena en buffer las respuestas del backend por defecto. Lee la respuesta completa del backend en memoria (o disco si excede el buffer), y luego la envía al cliente. Esto es eficiente para la mayoría de aplicaciones pero incorrecto para streaming, Server-Sent Events (SSE) o long-polling.

Directivas de timeout

Directiva Por defecto Recomendado Propósito
proxy_connect_timeout 60s 5-10s Tiempo para establecer conexión con el backend. Mantenlo corto para fallar rápido.
proxy_read_timeout 60s 60-300s Tiempo de espera para la respuesta del backend. Aumentar para APIs lentas.
proxy_send_timeout 60s 60s Tiempo para enviar el cuerpo de la petición al backend. Aumentar para subidas grandes.
location / {
    proxy_pass http://127.0.0.1:3000;

    proxy_connect_timeout 10s;
    proxy_read_timeout 120s;
    proxy_send_timeout 60s;
}

Estos timeouts son entre dos operaciones de lectura/escritura sucesivas, no para la petición completa. Una respuesta que envía datos cada 30 segundos nunca dispara un proxy_read_timeout de 60 segundos.

Controles de buffering

Directiva Por defecto Descripción
proxy_buffering on Almacena en buffer la respuesta completa del backend antes de enviar al cliente
proxy_buffer_size 4k o 8k Buffer para la primera parte de la respuesta (headers)
proxy_buffers 8 4k o 8 8k Número y tamaño de buffers para el cuerpo de la respuesta
proxy_busy_buffers_size 8k o 16k Tamaño máximo de buffers que pueden estar ocupados enviando al cliente

¿Cuándo desactivar proxy buffering?

Desactiva el buffering cuando el backend transmite datos en streaming: inferencia LLM (Ollama, vLLM), Server-Sent Events, respuestas largas tipo WebSocket o cualquier API que envíe datos chunked de forma incremental.

location /stream/ {
    proxy_pass http://127.0.0.1:8080;
    proxy_buffering off;
}

Mantén el buffering activado para APIs de petición/respuesta estándar. El buffering protege tu backend de clientes lentos: Nginx absorbe la respuesta rápidamente y la envía al cliente al ritmo del cliente. Sin buffering, un cliente lento mantiene abierta una conexión al backend.

Para ajuste avanzado de rendimiento, consulta .

¿Cómo solucionar errores 502 y 504?

502 Bad Gateway significa que Nginx no pudo conectar con el backend o que el backend envió una respuesta inválida. 504 Gateway Timeout significa que el backend no respondió dentro del proxy_read_timeout.

Lista de verificación para 502 Bad Gateway

  1. ¿Está el backend corriendo?
ss -tlnp | grep 3000

Si no hay salida, tu backend no está escuchando en el puerto 3000. Inícialo.

  1. ¿Es correcta la URL de proxy_pass? Busca errores tipográficos en el número de puerto o la dirección IP. Error frecuente: usar https:// para un backend que solo habla HTTP.

  2. ¿Está SELinux bloqueando la conexión? (RHEL/CentOS)

sudo setsebool -P httpd_can_network_connect 1
  1. Revisa el log de errores de Nginx:
sudo tail -20 /var/log/nginx/error.log

Busca connect() failed (111: Connection refused) o no live upstreams.

Lista de verificación para 504 Gateway Timeout

  1. Aumenta proxy_read_timeout:
proxy_read_timeout 300s;
  1. Comprueba si el backend es lento:
time curl http://127.0.0.1:3000/slow-endpoint

Si esto tarda más que tu proxy_read_timeout, aumenta el timeout u optimiza el backend.

  1. Comprueba la salud del upstream: si usas bloques upstream, todos los servidores podrían estar marcados como fallidos. Revisa el log de errores buscando no live upstreams while connecting to upstream.

Lectura de logs de Nginx

Nginx escribe los detalles de errores en /var/log/nginx/error.log. Para monitorización en tiempo real:

sudo journalctl -u nginx -f

O mira el log de errores directamente:

sudo tail -f /var/log/nginx/error.log

Para logs por sitio, añade las directivas access_log y error_log a tu server block:

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

    access_log /var/log/nginx/app-access.log;
    error_log /var/log/nginx/app-error.log;

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

Esto separa los logs por aplicación, facilitando la depuración de problemas con un backend específico.

Problemas frecuentes con headers

Si tu backend recibe 127.0.0.1 como IP del cliente en lugar de la dirección real, falta la directiva proxy_set_header X-Real-IP $remote_addr. Si tu aplicación genera enlaces HTTP usando http:// cuando debería usar https://, el header X-Forwarded-Proto no se está reenviando. Algunos frameworks (Express, Django, Rails) necesitan configuración explícita para confiar en los headers del proxy. En Express:

app.set('trust proxy', 1);

En Django, configura SECURE_PROXY_SSL_HEADER:

SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')

Sin estos ajustes, tu aplicación ignora los headers reenviados aunque Nginx los envíe correctamente.

Referencia completa: directivas de proxy

Para referencia rápida, aquí está el conjunto mínimo de directivas para diferentes escenarios de proxy:

# Standard HTTP proxy
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;
}

# WebSocket proxy
location /ws/ {
    proxy_pass http://127.0.0.1:8080;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection $connection_upgrade;
    proxy_read_timeout 3600s;
}

# Streaming proxy (SSE, LLM)
location /stream/ {
    proxy_pass http://127.0.0.1:11434;
    proxy_http_version 1.1;
    proxy_buffering off;
    proxy_cache off;
    proxy_read_timeout 600s;
}

Para server blocks gestionando múltiples dominios, consulta Nginx server blocks. Para hardening de seguridad incluyendo rate limiting y control de acceso, consulta .


Copyright 2026 Virtua.Cloud. Todos los derechos reservados. Este contenido es una obra original del equipo de Virtua.Cloud. La reproducción, republicación o redistribución sin permiso escrito está prohibida.

¿Listo para probarlo?

Despliega tu propio servidor en segundos. Linux, Windows o FreeBSD.

Ver planes VPS