Saltar al contenido principal

Proxy inverso

Resumen​

En una LAN no necesitas un proxy inverso — abre http://<host>:8080 y ya está.

Quieres uno cuando necesitas un nombre de host en vez de un puerto, HTTPS, o un solo punto de entrada frente a varias apps autoalojadas.

Los WebSockets no son opcionales

La UI de UltraTorrent es en vivo: progreso de torrents, salud de los motores, ejecuciones de RSS, progreso de importación — todo se envía por un WebSocket en /ws/. Un proxy que no reenvía los encabezados Upgrade y Connection produce una UI que aparenta estar bien, carga, te deja iniciar sesión — y luego nunca se actualiza. Las barras de progreso se quedan en 0%. Este es de lejos el error más común con proxies inversos, y todas las configuraciones de abajo lo manejan.

Mira este tutorial

Video próximamente.

Requisitos previos​

  • Una instalación con Docker Compose funcionando.
  • Un nombre DNS apuntando al host (para una instalación pública).
  • Los puertos 80/443 libres en el host, o un proxy que ya los tenga.

Requerimientos​

Un proxy inverso es barato: ~64 MB de RAM y un uso de CPU insignificante para cualquiera de las opciones de abajo. El único requerimiento real es que pueda hacer proxy de WebSockets.

Puertos​

PuertoQuién escuchaNotas
80 / 443Tu proxy inversoLos únicos puertos que deben mirar hacia el internet
8080 (host)contenedor frontendEnlázalo a 127.0.0.1 una vez que un proxy esté al frente — mira Mejores prácticas
8080 (contenedor)nginx dentro de la imagen frontendEl contenedor escucha en el 8080, no en el 80 — mira la nota de abajo
4000 (contenedor)backendSolo interno; nunca se publica
El contenedor frontend escucha en el 8080, no en el 80

La imagen se construye sobre nginx-unprivileged, que corre como uid 101 y por lo tanto no puede enlazarse a un puerto privilegiado. Dentro de la red de Docker el frontend es http://frontend:8080.

Esto muerde a las configuraciones de proxy escritas a mano: apuntar un upstream a frontend:80 da un 502 Bad Gateway, porque no hay nada escuchando ahí. Usa frontend:8080.

El deploy/Caddyfile que viene con el repo (el perfil proxy) enruta correctamente a frontend:8080. Si estás corriendo una copia anterior a esa corrección y el proxy incluido da 502, esa es la razón — actualízalo, o haz pull.

Volúmenes​

Solo el estado propio de tu proxy (certificados, cuenta ACME). Un proxy no toca los volúmenes de UltraTorrent.

Permisos​

Ninguno en específico — pero si tu proxy se enlaza al 80/443, necesita la capacidad de hacerlo (root, CAP_NET_BIND_SERVICE, o un container que ya la tenga).

Dos maneras de enrutar​

La Opción A es la que quieres. El nginx propio del frontend ya hace proxy de /api/ y /ws/ hacia el backend, con los encabezados de upgrade de WebSocket incluidos — así que un solo upstream te da una app completamente funcional, y hay exactamente un lugar que se puede configurar mal.

La Opción B (lo que hace el Caddyfile incluido) le quita un salto a las llamadas a la API. Úsala solo si tienes una razón; entonces tú mismo tienes que acertar con los encabezados de upgrade de /ws/.

Todas las configuraciones de abajo usan la Opción A a menos que se indique lo contrario.

Paso a paso​

1. Dile a UltraTorrent cuál es su origen público​

En .env:

CORS_ORIGIN=https://torrents.example.com

Luego recrea el backend:

docker compose up -d backend

La SPA llama a la API en una ruta relativa (/api), así que es del mismo origen y CORS es casi irrelevante — pero el valor se usa para los orígenes de navegador permitidos y debe reflejar la realidad. Acepta una lista separada por comas.

2. Enlaza el puerto de la UI a localhost​

Si el proxy corre en el mismo host, deja de exponer el 8080 a toda tu LAN:

# docker-compose.override.yml
services:
frontend:
ports: !override
- "127.0.0.1:8080:8080"
Verificado por la comunidad

!override requiere un Compose v2 reciente. Si tu versión lo rechaza, edita directamente la entrada ports: en docker-compose.yml — recuerda que un archivo de override normal añade puertos en vez de reemplazarlos.

Si el proxy corre en el mismo proyecto de Compose, elimina por completo el mapeo al host y deja que el proxy alcance frontend:8080 por la red internal.

3. Configura el proxy​

Etiquetas en el servicio frontend, asumiendo un Traefik con un entrypoint websecure y un resolver letsencrypt ya corriendo en una red compartida:

# docker-compose.override.yml
services:
frontend:
labels:
- "traefik.enable=true"
- "traefik.docker.network=traefik_proxy"
- "traefik.http.routers.ultratorrent.rule=Host(`torrents.example.com`)"
- "traefik.http.routers.ultratorrent.entrypoints=websecure"
- "traefik.http.routers.ultratorrent.tls.certresolver=letsencrypt"
# El contenedor escucha en el 8080 (nginx-unprivileged), NO en el 80.
- "traefik.http.services.ultratorrent.loadbalancer.server.port=8080"
networks: [internal, traefik_proxy]

networks:
traefik_proxy:
external: true

WebSockets: Traefik los pasa de forma transparente — sin middleware extra, sin malabares con encabezados. Simplemente funciona, siempre que no hayas eliminado Upgrade/Connection con un middleware headers personalizado.

Recordatorio de configuración estática (traefik.yml), si aún no la tienes:

entryPoints:
web:
address: ":80"
http:
redirections:
entryPoint: { to: websecure, scheme: https }
websecure:
address: ":443"

certificatesResolvers:
letsencrypt:
acme:
email: you@example.com
storage: /letsencrypt/acme.json
httpChallenge:
entryPoint: web

Verificación​

1. La página carga por HTTPS en tu nombre de host, con un candado válido.

2. La API responde a través del proxy:

curl -s https://torrents.example.com/api/system/live

3. El WebSocket hace upgrade. Esta es la prueba que de verdad importa:

curl -i -N \
-H "Connection: Upgrade" \
-H "Upgrade: websocket" \
-H "Sec-WebSocket-Version: 13" \
-H "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==" \
https://torrents.example.com/ws/

Lo esperado — el servidor acepta el upgrade:

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade

Cualquier otra cosa — 200, 400, 404, 502 — significa que tu proxy se está comiendo el upgrade. Regresa y arregla los encabezados Upgrade / Connection.

4. La prueba de verdad. Abre la UI, comienza una descarga y observa la barra de progreso moverse sin refrescar. Si tienes que presionar F5 para ver el progreso, el WebSocket no está conectado.

Panel Network de DevTools, filtro Socket: la petición /ws/ devuelve 101 Switching Protocols

Para comprobarlo tú mismo: DevTools → Network → Socket, y luego recarga. La petición ws/?EIO=4&transport=websocket tiene que reportar 101 Switching Protocols, con Connection: upgrade y Upgrade: websocket en los encabezados de respuesta. Cualquier otra cosa — casi siempre un 200 o un 400 — significa que tu proxy eliminó los encabezados de upgrade, y no hay reinicio de UltraTorrent que lo arregle.

HTTPS​

Todas las configuraciones de arriba asumen que TLS termina en el proxy. Certificados, Let's Encrypt, CAs personalizadas y DNS-01: TLS.

Actualizaciones​

Un proxy inverso es ortogonal a las actualizaciones de UltraTorrent — las reconstrucciones no lo tocan. Lo único que hay que volver a revisar tras una actualización: si editaste deploy/Caddyfile en el repo, un git pull puede entrar en conflicto con tu cambio. Mantén la configuración del proxy fuera del repo (o en docker-compose.override.yml, que no está bajo control de versiones) para que las actualizaciones se mantengan limpias.

Copias de seguridad​

Haz una copia de seguridad del almacén de certificados de tu proxy — caddy_data, el acme.json de Traefik, /etc/letsencrypt, o las carpetas data/ + letsencrypt/ de NPM. Perderlo es sobrevivible (los certificados se vuelven a emitir), pero llegarás a los límites de tasa de Let's Encrypt si lo haces repetidamente.

Resolución de problemas​

SíntomaCausaSolución
502 Bad Gateway desde el proxy Caddy incluidodeploy/Caddyfile enruta a frontend:80, pero la imagen nginx-unprivileged escucha en el 8080Cámbialo a reverse_proxy frontend:8080
502 / conexión rechazada en generalEl proxy no puede alcanzar el upstream — puerto equivocado, nombre de host equivocado, o no están en la misma red de DockerEl upstream es frontend:8080 (misma red) o 127.0.0.1:8080 (mismo host). Conecta el proxy a la red internal si usas nombres de servicio
La UI carga, inicias sesión, pero nada se actualiza nuncaEl upgrade de /ws/ se está descartandoEjecuta la prueba de curl del 101 Switching Protocols de arriba. Añade los encabezados Upgrade/Connection (NGINX), habilita Websockets Support (NPM), o pon timeout tunnel (HAProxy)
Las actualizaciones en vivo funcionan por ~60 segundos y luego paranEl tiempo de espera de lectura del proxy está matando el WebSocket inactivoproxy_read_timeout 86400s (NGINX/NPM), timeout tunnel 24h (HAProxy)
El login funciona pero al refrescar te saca la sesiónCookies/encabezados alterados, o falta X-Forwarded-Proto y la app cree que está en HTTPReenvía Host y X-Forwarded-Proto
Los recursos estáticos dan 404 bajo una subruta (p. ej. /ultratorrent/)La SPA está construida para la ruta raíz — las URLs de sus recursos son absolutasSirve UltraTorrent en su propio nombre de host o subdominio, no en una subruta
Al subir un archivo .torrent → 413 Request Entity Too LargeLímite de tamaño del cuerpo en el proxyclient_max_body_size 64m (NGINX/NPM)
La emisión del certificado fallaEl DNS todavía no apunta al proxy, o el 80/443 no es alcanzable para el desafío HTTP-01Mira TLS
Todo funciona en la LAN, nada desde afueraFirewall o routerSolo el 80/443 debe reenviarse — y nunca el 4000 ni el 5000

Mejores prácticas​

  • Termina TLS en el proxy, y deja que el proxy sea lo único en el 80/443.
  • Usa la Opción A (un solo upstream, frontend:8080) a menos que tengas una razón específica para no hacerlo. Menos lugares donde equivocarte con el WebSocket.
  • Enlaza el puerto del host del contenedor a 127.0.0.1 una vez que un proxy esté al frente, para que el puerto HTTP en crudo no sea alcanzable desde tu LAN.
  • Nunca expongas el backend (4000) ni SCGI (5000) al internet mediante proxy. La API ya es alcanzable a través de /api/; SCGI es control remoto sin autenticación.
  • Configura tiempos de espera de lectura/túnel largos — un WebSocket es, por diseño, una conexión inactiva de larga duración.
  • Mantén la configuración del proxy fuera del repo para que git pull nunca entre en conflicto con ella.
  • Dale su propio nombre de host, no una subruta.
  • Añade autenticación al frente si es público — una política de Cloudflare Access, o la autenticación básica de tu proxy — como defensa en profundidad sobre el propio login de UltraTorrent. Mira Seguridad.

Preguntas frecuentes​

¿Necesito un proxy inverso en mi LAN? No. http://<host>:8080 funciona bien.

¿Puedo correr UltraTorrent bajo example.com/torrents? No está soportado — la SPA está construida para la ruta raíz. Usa un subdominio.

¿Cuál proxy da menos problemas? Caddy o Traefik: ambos manejan los upgrades de WebSocket de forma transparente y emiten certificados por sí mismos.

¿Todavía necesito el perfil proxy incluido si corro mi propio proxy? No. No lo inicies — pelearía por los puertos 80/443.

¿El WebSocket necesita su propio nombre de host o puerto? No. Se sirve en /ws/ en el mismo origen que la UI.

¿Por qué la prueba de conexión a Prowlarr pasa pero las descargas fallan? Eso no es un problema del proxy — es la protección contra SSRF. Mira Docker Compose → perfiles opcionales.

Lista de verificación​

  • El proxy alcanza el upstream — frontend:8080 (misma red) o 127.0.0.1:8080 (mismo host)
  • /ws/ devuelve 101 Switching Protocols a través del proxy
  • Los tiempos de espera de lectura / túnel están muy por encima de 60 segundos
  • client_max_body_size (o su equivalente) de al menos 64 MB
  • CORS_ORIGIN en .env coincide con la URL pública; backend recreado
  • HTTPS funciona, y HTTP redirige hacia él
  • El puerto 8080 del host está enlazado a 127.0.0.1 (o eliminado por completo)
  • El backend (4000) y SCGI (5000) no están expuestos
  • La barra de progreso de una descarga se mueve en vivo, sin refrescar la página
  • La configuración del proxy vive fuera del repo de git

Mira también​