Problema

Los usuarios que ejecutan servicios auto‑alojados (Immich, Nextcloud, Jellyfin, etc.) detrás de un CGNAT suelen recurrir a Cloudflare Tunnel para exponer sus aplicaciones sin una IP pública. La solución funciona bien para tráfico web típico, pero Cloudflare impone un límite de 100 MB al cuerpo de la petición cuando el túnel actúa como proxy. Subidas de vídeos, backups de fotos o cualquier archivo grande se interrumpen con errores de “request entity too large”. El síntoma es idéntico sin importar la aplicación: la carga falla justo después de alcanzar los 100 MB y el cliente muestra un mensaje de error de Cloudflare.

Causa

  1. Restricción de Cloudflare Tunnel – En el modo “free” y en la mayoría de planes, Cloudflare corta el cuerpo de la petición a 100 MB para proteger su red de abusos. No hay opción de aumentar el límite sin pasar a un plan Enterprise, lo que resulta inviable para la mayoría de homelabs.

  2. CGNAT sin IP pública – La ausencia de una dirección IPv4 pública impide abrir puertos directamente en el router. Sin NAT‑loopback o port‑forward, el tráfico externo debe pasar por un proxy o túnel.

  3. Dependencia de un solo túnel – Cuando todo el tráfico (incluido el de subida) se enruta por Cloudflare, cualquier limitación del túnel afecta a todas las aplicaciones, incluso aquellas que no necesitan protección DDoS.

  4. Falta de solución “seamless” – Herramientas como Tailscale ofrecen túneles sin límite, pero requieren que el cliente (p. ej. el móvil de la esposa) active la red cada vez, lo que rompe la experiencia de usuario.

Solución

Arquitectura recomendada

  1. VPS de bajo costo (4‑5 USD/mes) como punto de terminación pública.
  2. WireGuard (o Tailscale) entre el VPS y la red doméstica. La conexión es punto‑a‑punto, cifrada y sin límite de tamaño.
  3. Reverse proxy (Caddy, Nginx o Traefik) en el VPS que expone los dominios públicos y reenvía el tráfico a la IP interna del hogar a través del túnel WireGuard.
  4. Split‑brain DNS (AdGuard Home, Pi‑hole o CoreDNS) para que dentro de la red local los nombres resuelvan directamente al IP interno, evitando el salto por el VPS y manteniendo la latencia mínima.

Con esta configuración, el tráfico de subida grande no pasa por Cloudflare sino por el túnel WireGuard, que no impone límite de cuerpo. Cloudflare solo se usa para DNS y opcionalmente para TLS terminación si se desea, pero sin habilitar el “proxy” (modo DNS‑only) para los subdominios que manejan archivos grandes.

Paso a paso

  1. Crear la instancia VPS

    • Sistema operativo: Debian/Ubuntu (cualquier distro ligera).
    • Abrir puertos UDP 51820 (WireGuard) y TCP 80/443 (reverse proxy).
  2. Instalar WireGuard en VPS y en el router/home‑server

    • Generar pares de claves (wg genkey | tee privatekey | wg pubkey > publickey).
    • Configurar /etc/wireguard/wg0.conf en ambos extremos con la IP de la red privada (p. ej. 10.0.0.0/24).
  3. Levantar el túnel

    wg-quick up wg0
    

    Verificar que ping 10.0.0.2 (IP del home‑server) responde.

  4. Instalar Caddy en el VPS (ejemplo porque gestiona TLS automáticamente).

    • Archivo Caddyfile básico:
    immich.mydomain.com {
        reverse_proxy 10.0.0.2:3000
    }
    
    nextcloud.mydomain.com {
        reverse_proxy 10.0.0.2:8080
    }
    
    # dominios que no requieren gran ancho de banda pueden seguir usando Cloudflare proxy
    
    • Ejecutar caddy run o crear un servicio systemd.
  5. Configurar DNS

    • En Cloudflare, establecer los registros A de los subdominios a la IP del VPS y marcar DNS only (nube gris).
    • En AdGuard Home, crear una regla de “Conditional Forwarding” que resuelva *.mydomain.com a la IP interna cuando la consulta provenga de la red local.
  6. Ajustar firewall

    • En el router, permitir UDP 51820 hacia el VPS.
    • En el VPS, limitar el acceso al puerto 443 al rango de IP interno del túnel para evitar exposición directa.

Alternativas

  • Tailscale + Exit Node: Configurar el VPS como “exit node” y que los clientes móviles lo usen automáticamente. Requiere menos configuración de WireGuard, pero depende del cliente Tailscale.
  • FRP (Fast Reverse Proxy): Un binario que corre en el VPS y en el hogar, reenvía puertos sin límite. Menos flexible que WireGuard para múltiples servicios, pero útil si solo se necesita un puerto.
  • Rathole: Similar a FRP, pero con soporte para TLS y autenticación por token.

Cuándo aplicar esta solución

  • Síntomas: Subidas que fallan alrededor de 100 MB, errores de “request entity too large”, o logs de Cloudflare indicando “body size limit exceeded”.
  • Entorno: Red doméstica detrás de CGNAT, sin IP pública, y con necesidad de exponer varios servicios (media, backup, gestión de archivos).
  • Requisitos: Se desea una experiencia “plug‑and‑play” para dispositivos móviles y clientes de escritorio, sin intervención manual para activar VPN.

No aplicar si:

  • Sólo se necesita exponer una aplicación ligera y la limitación de 100 MB no afecta.
  • Se dispone de una IP pública estática y se prefiere abrir puertos directamente.

Código

# Generar claves en el VPS
wg genkey | tee /etc/wireguard/server_private.key | wg pubkey > /etc/wireguard/server_public.key

# Generar claves en el home‑server
wg genkey | tee /etc/wireguard/client_private.key | wg pubkey > /etc/wireguard/client_public.key

# Configuración del servidor (VPS) - /etc/wireguard/wg0.conf
[Interface]
Address = 10.0.0.1/24
ListenPort = 51820
PrivateKey = <contenido de server_private.key>

[Peer]
PublicKey = <contenido de client_public.key>
AllowedIPs = 10.0.0.2/32
PersistentKeepalive = 25

# Configuración del cliente (home‑server) - /etc/wireguard/wg0.conf
[Interface]
Address = 10.0.0.2/24
PrivateKey = <contenido de client_private.key>

[Peer]
PublicKey = <contenido de server_public.key>
Endpoint = <IP_VPS>:51820
AllowedIPs = 0.0.0.0/0, ::/0
PersistentKeepalive = 25

Verificación

  1. Conexión WireGuard

    • wg show debe listar el peer con latest handshake reciente.
    • ping 10.0.0.1 desde el home‑server y viceversa deben responder sin pérdida.
  2. Reverse proxy

    • En un navegador externo, abrir https://immich.mydomain.com.
    • Verificar que la página carga y que la IP del backend (10.0.0.2) no aparece en los logs de Cloudflare.
  3. Subida de archivo grande

    • Desde la app móvil o cliente web, subir un archivo > 200 MB.
    • La operación debe completarse sin errores y sin aparecer en los logs de Cloudflare como “body size limit”.
  4. DNS interno

    • Dentro de la red local, ejecutar dig immich.mydomain.com y confirmar que la respuesta es la IP interna del reverse proxy (p. ej. 192.168.1.10).

Notas adicionales

  • TLS en el VPS: Caddy gestiona certificados automáticamente con Let’s Encrypt, pero solo si el dominio apunta a la IP pública del VPS.
  • Persistencia de WireGuard: Añadir PostUp y PostDown en la configuración para actualizar reglas de iptables y evitar que el tráfico salga por la interfaz pública.
  • Monitorización: Un pequeño contenedor de watchtower o prometheus-node-exporter en el VPS ayuda a detectar caídas del túnel antes de que los usuarios noten el problema.
  • Coste: Un VPS de 1 vCPU y 1 GB RAM suele costar entre 3 y 5 USD/mes, suficiente para manejar varios GB/s de tráfico interno sin saturarse.
  • Escalado: Si la carga de subida supera la capacidad del enlace de fibra (p. ej. varios TB/día), considerar balancear el tráfico entre varios VPS o usar un servicio de “exit node” dedicado con mayor ancho de banda.