Cómo sortear el CGNAT para publicar tu blog o cualquier otra cosa
Durante años he tenido mis servicios autoalojados accesibles solo desde la red local. Immich, Navidrome, Castopod, Miniflux, este blog... todo funcionaba, pero solo desde casa. Cuando salía, no había forma de acceder a nada.
El problema es el CGNAT (Carrier-Grade NAT). Mi operador, como muchos otros, no me da una IP pública real, sino que me mete detrás de una NAT gigante que comparten cientos de clientes. Eso significa que no puedo abrir puertos, porque el router no tiene una IP pública a la que abrirlos. Y además, mi IP es dinámica: aunque pudiera abrir puertos, cambiaría cada cierto tiempo.
Durante un tiempo lo solucioné con una VPN (WireGuard), pero eso solo me servía a mí. Para que un blog sea público, cualquiera tiene que poder entrar sin instalar nada.
Este es el camino que encontré: Cloudflare Tunnel.
Qué es Cloudflare Tunnel
Un túnel inverso. En lugar de que internet entre a tu casa (lo que CGNAT impide), tu máquina abre una conexión saliente a Cloudflare. Cloudflare te pasa el tráfico entrante por esa conexión ya establecida.
Ventajas:
- No abres puertos. No expones tu IP.
- Funciona con CGNAT y con IP dinámica.
- TLS gratis.
- Protección DDoS incluida.
- No necesitas un dominio propio para probar (aunque sí lo necesitarás para producción).
Desventajas:
- Dependes de Cloudflare. Si Cloudflare cae, tu blog cae.
- Cloudflare ve el tráfico (al menos los metadatos, y el contenido si no cifras extremo a extremo).
- El plan gratuito tiene límites (razonables para un blog personal).
Para mi caso (blog personal, servicios autoalojados), las ventajas superan claramente las desventajas.
Instalación en Docker
Cloudflare Tunnel se instala como un contenedor cloudflared en la Raspberry Pi. El mío:
services:
cloudflared:
image: cloudflare/cloudflared:latest
restart: unless-stopped
command: tunnel run
environment:
- TUNNEL_TOKEN=[TU_TOKEN]
volumes:
- /root/misApps/cloudflared:/etc/cloudflared
networks: {}
El TUNNEL_TOKEN lo obtienes del dashboard de Cloudflare (Zero Trust → Networks → Tunnels → Create a tunnel). Es el "pegamento" entre tu contenedor y tu cuenta.
La pieza clave: conectar el túnel con NPM
Aquí es donde mucha gente se pierde. El túnel no habla directamente con el blog. El túnel habla con Nginx Proxy Manager, y NPM se encarga de enrutar por dominio.
Mi flujo es:
Cloudflare Tunnel
↓ (HTTP a http://192.168.1.X:81)
Nginx Proxy Manager (escucha en el 81)
↓ (Proxy Host: blog.elsultan.casa)
Sirve el estático desde /blog
En el dashboard de Cloudflare, cada "Public Hostname" del túnel apunta a un servicio interno. El del blog:
- Subdomain:
blog - Domain:
elsultan.casa - Type:
HTTP - URL:
192.168.1.X:81(la IP fija de la RPi donde escucha NPM)
Uso IP y puerto fijos porque tengo fijadas las IPs de los miembros principales del homelab. Es la forma más sencilla y predecible.
NPM: el blog como estático
En NPM, el blog no es un "Proxy Host" normal que redirige a otro servicio. Es un servidor de estáticos. El Proxy Host apunta a http://127.0.0.1:80 (interno al contenedor de NPM) y en la pestaña Advanced tiene:
location / {
root /blog;
try_files $uri $uri/ $uri/index.html =404;
add_header Cache-Control "no-cache" always;
}
location ~* ^/(css|images)/ {
root /blog;
expires 7d;
add_header Cache-Control "public, max-age=604800" always;
}
error_page 404 /404.html;
location = /404.html {
root /blog;
internal;
}
Y el docker-compose.yml de NPM monta el directorio del blog:
volumes:
- /root/misBackup/sync/bssg-output:/blog:ro
El :ro es importante: NPM solo lee, nunca escribe. El blog lo genero con BSSG en mi PC, lo sincronizo a la RPi con Syncthing, y NPM lo sirve tal cual.
Lo que expongo (y cómo lo protejo)
Con el túnel, expongo:
- Este blog.
- Immich (fotos).
- Navidrome (música).
- Castopod (podcast).
- Miniflux (RSS).
El blog es el único público de verdad: cualquiera puede leerlo sin registrarse. El resto son accesibles desde internet, pero requieren login. Cualquiera puede llegar a la pantalla de acceso, pero no debería pasar de ahí.
No uso Cloudflare Access para protegerlos. Es una decisión consciente: todos mis servicios ya tienen su propio sistema de autenticación, y meter Cloudflare Access encima sería un engorro más que un beneficio. Sobre todo para Immich, que lo usa gente que no es técnica y tendría que pasar dos logins para ver sus fotos.
Conclusión
CGNAT ya no es un impedimento para el selfhosting. Cloudflare Tunnel resuelve el problema de forma limpia: sin abrir puertos, sin exponer tu IP, con TLS gratis y funcionando con IP dinámica.
Si tienes un homelab y tu operador te tiene detrás de CGNAT, esta es la forma más sencilla que he encontrado de sacar servicios a internet. En posts anteriores he contado cómo monto el NAS, las aplicaciones que tengo en él y cómo NPM me ayuda a enrutar todo esto. Este es el eslabón que faltaba: cómo hacerlo accesible desde fuera.