hjs.es ← inicio

Acceso remoto seguro a Proxmox VE con Cloudflare Tunnel

Diseño e implementación de una arquitectura de acceso remoto desde una red corporativa con restricciones estrictas. Tres intentos fallidos, una solución que combina Cloudflare Tunnel, Let's Encrypt vía ACME y Cloudflare Access con doble factor.

Proxmox VE Cloudflare Tunnel Let's Encrypt ACME / DNS-01 Cloudflare Access LXC Zero Trust

Contexto

El objetivo era poder gestionar un servidor Proxmox VE doméstico de forma remota desde una red externa con restricciones. El entorno tenía tres restricciones combinadas que cerraron las soluciones habituales y obligaron a buscar una arquitectura alternativa.

01
Red corporativa con políticas estrictas Sin posibilidad de instalar software adicional en los equipos de esa red — cualquier solución que requiriese un cliente instalado quedaba descartada desde el principio.
02
ISP doméstico con NAT El router del operador no permite modo bridge ni IP pass-through, por lo que no hay IP pública real asignada al router doméstico — abrir puertos directamente no era una opción.
03
VPS intermedio sobre OpenVZ/Virtuozzo El VPS disponible como intermediario corre sobre virtualización a nivel de contenedor, sin acceso al módulo TUN/TAP del kernel — descartando toda la familia de soluciones basadas en interfaces de red virtuales.

Intentos fallidos

✗

Intento 1 — Tailscale / WireGuard

La primera opción natural para acceso remoto a una red privada es una VPN mesh tipo Tailscale o WireGuard.
Bloqueante (red corporativa): Tailscale requiere instalar un cliente en el equipo de destino. Las políticas de software de la red corporativa lo impiden sin aprobación de IT.
Bloqueante (VPS): El VPS corre sobre OpenVZ/Virtuozzo, que no expone el módulo tun del kernel. Sin ese módulo, cualquier solución VPN basada en interfaces virtuales es inviable — el kernel no puede crear la interfaz de red virtual necesaria.
✗

Intento 2 — Túnel SSH inverso con VPS como relay

Con VPN descartada, la siguiente opción fue un túnel SSH inverso: Proxmox establece una conexión SSH saliente hacia el VPS, el VPS expone un puerto en localhost, y desde el equipo cliente se abre un segundo túnel SSH hacia ese puerto. Esta arquitectura no requiere instalar nada en el equipo cliente (OpenSSH está disponible por defecto en Windows 10/11) y no depende de módulos de kernel especiales.

Se implementó completamente:

  • Usuario tunnel sin shell en ambas máquinas, solo para túneles
  • Par de claves ed25519 dedicado con restricciones en authorized_keys
  • autossh como servicio systemd en Proxmox para recuperación automática
  • iptables en el VPS restringiendo el puerto del túnel a una IP autorizada
  • GatewayPorts no para que el puerto no sea accesible desde internet
Bloqueante: El VPS (OpenVZ) tiene restricciones de red que impiden enrutar tráfico hacia rangos de IPs privadas RFC1918. El túnel SSH se establecía correctamente (conexión TCP confirmada), pero cuando el VPS intentaba reenviar el tráfico hacia 192.168.x.x:8006, el paquete no llegaba al destino — 100% packet loss desde el VPS hacia cualquier IP privada.
✗

Intento 3 — Cloudflare Tunnel (primer intento)

Cloudflare Tunnel permite exponer servicios internos a través de una conexión saliente desde un LXC hacia la infraestructura de Cloudflare, sin abrir puertos en el router. No requiere IP pública estática, no depende del ISP, y no necesita instalar nada en la red de destino.

Bloqueante: Proxmox usa por defecto un certificado TLS autofirmado. Con Full (Strict) lo rechazaba. Con Full daba error 502 porque cloudflared no podía verificar el certificado. La opción noTLSVerify sí existe en el dashboard de Cloudflare Zero Trust (en los ajustes adicionales de la aplicación publicada, sección TLS → No TLS Verify), pero en su momento no la localicé y probé antes la vía del fichero de configuración YAML. Aun así, desactivar la verificación deja sin validar el tramo túnel → Proxmox: la solución que acabé adoptando (un certificado válido) mantiene la verificación estricta.
  • Modificar el servicio systemd para usar config YAML con noTLSVerify: true → conflicto con credenciales (el túnel por token no usa fichero de credenciales local)
  • Cambiar el modo SSL a Full en vez de Full (Strict) → seguía dando 502 por el certificado autofirmado

Solución final

La arquitectura definitiva combina tres componentes que resuelven conjuntamente lo que ninguno resolvía por separado.

1

LXC dedicado para cloudflared

Antes de nada, un LXC mínimo dedicado solo a correr cloudflared — separado de Proxmox y de cualquier otro servicio, para que el túnel no dependa de ninguna otra pieza del homelab. Ahí se configura todo lo relativo al túnel: instalación de cloudflared, autenticación con Cloudflare, creación del túnel y el public hostname que lo enlaza con Proxmox.

Al ser el punto único por el que se accede a Proxmox en remoto, este LXC tiene que estar operativo 24/7 — incluido justo después de un reinicio del propio Proxmox, sin intervención manual. Eso significa activar Start at boot (onboot: 1) en las opciones del contenedor.

2

Certificado Let's Encrypt en Proxmox via ACME

Se emite un certificado válido directamente en Proxmox usando el challenge DNS-01 de Let's Encrypt con el plugin de Cloudflare. Esto elimina el certificado autofirmado sin necesidad de exponer el puerto 80 a internet.

El plugin de Cloudflare crea automáticamente un registro TXT _acme-challenge en el DNS, Let's Encrypt lo verifica, y emite el certificado. Proxmox lo instala y renueva automáticamente a partir de los 60 días de vida (el certificado dura 90).

3

Cloudflare Tunnel con resolución DNS local

Con el certificado válido, Cloudflare Tunnel puede conectar con verificación Full (Strict). Sin embargo, surgía un problema adicional: cuando cloudflared intentaba conectar a homelab.hjs.es:8006, el DNS resolvía a la IP pública de Cloudflare (no a la IP local de Proxmox), creando un bucle.

Solución: entrada en /etc/hosts del LXC de cloudflared apuntando el dominio a la IP local de Proxmox. El certificado coincide con el hostname y la conexión TLS valida correctamente.

4

Cloudflare Access con doble factor

Delante del túnel, Cloudflare Access actúa como capa de autenticación con dos reglas obligatorias (AND): restricción por IP de origen autorizada + OTP por email de un solo uso.

  1. Navegador accede a https://homelab.hjs.es
  2. Cloudflare Access intercepta → pantalla de email
  3. Usuario introduce email → recibe código OTP por email
  4. Introduce el código → sesión válida por 24h
  5. Llega al login de Proxmox VE con certificado válido, sin avisos de seguridad

Arquitectura final

arquitectura.txt
Red restringida (cliente)
        │
        │ HTTPS puerto 443
        ▼
Cloudflare Edge
        │
        │ Cloudflare Access
        │   → verifica IP autorizada
        │   → verifica OTP email
        ▼
Cloudflare Tunnel
        │
        │ conexión saliente iniciada desde el LXC
        │ sin puertos abiertos en el router doméstico
        ▼
LXC cloudflared  /etc/hosts → IP local de Proxmox
        │
        │ HTTPS con certificado Let's Encrypt válido
        ▼
Proxmox VE :8006
        certificado: homelab.hjs.es
        emisor: Let's Encrypt
        renovación: automática via ACME + Cloudflare DNS plugin

Stack y seguridad

Stack tecnológico

HipervisorProxmox VE 9.2 (Debian 13)
Contenedor tunnelLXC Debian 13, cloudflared 2026.6.1
Certificado TLSLet's Encrypt via ACME (DNS-01)
DNS y CDNCloudflare (plan Free)
AutenticaciónCloudflare Access (Zero Trust)
HardwareIntel i5 6ª gen · 16 GB RAM · 240 GB SSD

Capas de seguridad

Sin puertos abiertosCloudflare Tunnel (conexión saliente)
Restricción de redCloudflare Access: solo IP autorizada
AutenticaciónOTP por email, segundo factor obligatorio
Cifrado TLSLet's Encrypt, Full Strict, TLS 1.3
Sesión limitadaExpiración automática en 24h

Lecciones aprendidas

OpenVZ/Virtuozzo limita más de lo que parece La falta del módulo TUN/TAP y las restricciones de enrutamiento hacia IPs privadas eliminan una familia entera de soluciones. Para infraestructura propia, KVM es el mínimo recomendable.
"Full Strict" vs "Full" en Cloudflare no es solo semántica La diferencia entre verificar o no el certificado del origen determinó si el túnel funcionaba o daba 502. Sin un certificado válido en el origen, Full Strict es inviable.
El DNS local importa en arquitecturas de túnel Cuando el dominio público y el servicio interno comparten nombre, el LXC de cloudflared necesita resolver el dominio a la IP local vía /etc/hosts para que la validación TLS funcione correctamente.
Los problemas reales rara vez tienen una solución directa La solución final requirió combinar tres tecnologías distintas para resolver restricciones que ninguna de ellas resolvía individualmente.

Notas rápidas

Referencia de uso diario para mantener este acceso, sin tener que reconstruir el razonamiento cada vez.

Crear el LXC de cloudflared y dejarlo arrancando solo

Para levantar el LXC dedicado a cloudflared sin instalarlo todo a mano, uso el script de Proxmox VE Helper-Scripts (comunidad, antes tteck). Se ejecuta desde la shell del propio Proxmox:

bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/cloudflared.sh)"

Documentación del script: community-scripts.org/scripts/cloudflared. Tras crear el LXC, dentro del contenedor: cloudflared tunnel login y cloudflared tunnel create <nombre> (o crear el túnel directamente desde el dashboard de Cloudflare Zero Trust → Networks → Tunnels).

Como este LXC tiene que estar arriba siempre — incluido justo después de reiniciar Proxmox —, hay que activar Start at boot:

# Desde la shell de Proxmox (sustituye <ID_LXC> por el ID real, ej. 101):
pct set <ID_LXC> --onboot 1

# Comprobar que ha quedado activado:
pct config <ID_LXC> | grep onboot

O desde la GUI: selecciona el LXC → pestaña Options → Start at boot → Edit → Yes.

Renovar o forzar el certificado manualmente

La renovación es automática, pero si hace falta comprobarla o forzarla:

# Ver estado y fecha de expiración del certificado activo:
pvenode cert info

# Forzar renovación manual vía ACME:
pvenode acme cert renew
Comprobar el túnel de Cloudflare desde el LXC
# Estado del servicio cloudflared:
systemctl status cloudflared

# Confirmar que /etc/hosts resuelve al Proxmox local, no a Cloudflare:
cat /etc/hosts | grep homelab.hjs.es
Si Cloudflare Access no deja pasar el OTP

Revisar en el dashboard de Cloudflare Zero Trust → Access → Applications, que la regla de IP autorizada siga apuntando a la IP pública actual de la red de origen (puede cambiar si su ISP no ofrece IP fija).