Publicar un servidor doméstico a través de un VPS cuando CGNAT bloquea los puertos
El camino del navegador a la máquina de casa: demostrar que la línea está tras CGNAT, apuntar el DNS al VPS, hacer DNAT de un puerto, arreglar la ruta de vuelta y verificar desde fuera.
Empecé desde fuera, como lo haría un usuario: escribí el nombre en el navegador de un teléfono con datos móviles y vi cómo la conexión se quedaba colgada hasta agotar el tiempo. El nombre se resolvía, así que el DNS estaba bien. La página de redirección de puertos del router de casa también era un callejón sin salida, porque su dirección WAN marcaba 100.64.31.42, y esa no es una dirección a la que un proveedor me enrute desde internet. Ese número explica todo el fallo: la línea está detrás de carrier-grade NAT, así que la dirección pública de mi lado del túnel pertenece al proveedor. Lo que sigue es el camino que recorrí desde esa pestaña colgada hasta un servicio que responde.
Empezar por el cliente y avanzar hacia dentro
Antes de cambiar ninguna regla, demuestra dónde se detiene la cadena. En el portátil ejecuté dig +short files.example.net y obtuve la dirección del VPS, lo que descartaba un DNS obsoleto en mi máquina. Después, curl -v --connect-timeout 5 files.example.net:5022 agotó el tiempo en lugar de ser rechazado. Esa diferencia importa: un tiempo agotado suele significar que se están descartando paquetes por el camino, mientras que un rechazo significa que algo respondió y dijo que no.
El siguiente salto hacia dentro era el propio VPS. ss -ltnp no mostró nada escuchando en 5022, lo cual era esperable antes de que existiera la regla DNAT. En la misma máquina, wg show (o ip -br a show tun0 para OpenVPN) confirmó que el par de casa estaba activo con un handshake reciente. Si el par está caído, ninguna regla NAT ayudará, y la luz verde de la página VPN del router de casa no significa nada.
Confirmar que la línea de casa está realmente tras CGNAT
Compara la dirección que el router informa en su interfaz WAN con lo que ve un servicio externo. Todo lo que esté en 100.64.0.0/10, es decir, 100.64.x.x hasta 100.127.x.x, es el rango de direcciones compartidas del RFC 6598 y no se puede alcanzar desde internet. Llegas a la misma conclusión cuando el router dice tener una dirección privada normal y una consulta pública devuelve algo que no tiene relación. Una línea CGNAT también puede venir con una dirección pública que solo dura hasta el siguiente arrendamiento, así que una redirección de puerto parece funcionar una hora y luego muere en silencio.
Algunos proveedores sí entregan una dirección pública real, pero bloquean los puertos entrantes 80 y 443 en cuentas residenciales. Si tu servicio es solo web y la dirección WAN es realmente pública, pregunta al soporte antes de construir un relé. Si no lo es, el diseño de abajo se aplica a todos los puertos por igual, y merece la pena detener el experimento en el lado del router: el traductor de la red del proveedor termina la conexión antes de que llegue a tu interfaz WAN.
Apuntar el nombre a la dirección del VPS
El DNS no retransmite tráfico, así que el registro A del servicio debe contener la dirección pública del VPS. Un registro A hacia 192.168.1.20 solo se resuelve de forma útil para clientes que ya están dentro de la VPN; desde internet se resuelve a una dirección que nadie puede enrutar. Para el acceso de administración del propio VPS mantengo un nombre aparte, para no confundir nunca a qué host apunta un registro.
Si tanto el VPS como la máquina de casa sirven HTTP, dale a cada uno su propio nombre y deja que el VPS decida cuál responde. Fundirlos en un solo nombre y esperar que el proxy elija el backend correcto es exactamente cómo acabas depurando el host equivocado durante toda una tarde.
Un puerto en DNAT, SSH se queda en 22
La opción directa es NAT de destino en el VPS. Activa el reenvío para el núcleo en ejecución y mantén la dirección de túnel del servidor de casa en la regla, no su dirección LAN:
sysctl -w net.ipv4.ip_forward=1
iptables -t nat -A PREROUTING -p tcp -d 198.51.100.10 --dport 5022 -j DNAT --to-destination 10.8.0.2
Deja SSH al propio VPS en 22 y reenvía otro puerto, por ejemplo 5022, a la máquina de casa. Si reenvías 22 a una máquina apagada, pierdes el único camino que podría haberla arreglado. La misma lógica se aplica al proxy: deja el puerto de administración del VPS fuera de las reglas de relé.
Haz persistentes las reglas en cuanto funcionen. En Debian o Ubuntu, apt install iptables-persistent y luego netfilter-persistent save las escriben en /etc/iptables/rules.v4. En un host con firewalld el equivalente es un reenvío de puerto permanente en la zona, que necesita firewall-cmd --reload antes de aplicarse. Las reglas que solo viven en memoria desaparecen en el primer reinicio de mantenimiento del proveedor, normalmente a mala hora.
Arreglar la ruta de vuelta, o evitar necesitarla
DNAT reescribe solo el destino. El servidor de casa ve una conexión desde la dirección del cliente original, y si su ruta por defecto apunta al router de casa, la respuesta sale por la WAN doméstica donde el traductor del proveedor la descarta. Esa asimetría fue el segundo fallo con el que me topé, después de que la regla DNAT pareciera completamente correcta.
Dos arreglos funcionan. Dale a la máquina de casa una ruta de política para que el tráfico del túnel vuelva por el túnel:
ip rule add from 10.8.0.2 table 100
ip route add default dev wg0 table 100
O deja que el VPS reescriba el origen para que la respuesta tenga una dirección que pueda alcanzar, y deja que conntrack la invierta a la salida:
iptables -t nat -A POSTROUTING -d 10.8.0.2 -p tcp --dport 5022 -j SNAT --to-source 10.8.0.1
La versión SNAT oculta la dirección real del cliente al servidor de casa, así que los registros de la aplicación muestran 10.8.0.1 como par. Pasa la dirección original en la capa de aplicación, o acepta perder el registro por cliente, pero decídelo a conciencia en lugar de descubrirlo durante un incidente.
Comprobar todo el camino desde fuera
Verifica desde una red que no sea ni el VPS ni la casa: un teléfono con datos móviles, o un shell barato en otro sitio. nc -vz files.example.net 5022 debería informar de una conexión correcta, y openssl s_client -connect files.example.net:443 -servername files.example.net debería mostrar el certificado del nombre público. Luego revisa el grupo de seguridad o el cortafuegos en la nube del proveedor. Una regla DNAT que nunca ve un paquete suele estar bloqueada una capa por encima del host.
Por último, decide qué pasa cuando el túnel se cae. Una regla DNAT sigue aceptando conexiones y las alimenta en silencio hacia un túnel muerto, lo que se parece exactamente a un servicio roto. Una comprobación de salud en el VPS que rechace el puerto mientras el par está caído cuesta unas pocas líneas y ahorra una hora de suposiciones. Después, deja de editar reglas y obsérvalo un día antes de confiar en él.