Nada de abrir puertos
El demonio del túnel mantiene conexiones salientes hacia Cloudflare, así que el tráfico entra sin que nada escuche en el router y la IP de casa no se publica nunca.
/ Caso · Infraestructura self-hosted
Un mini PC de segunda mano en mi salón con ocho servicios en Docker, accesible desde internet por un túnel saliente de Cloudflare y desde mis dispositivos por Tailscale, sin un solo puerto abierto en el router. Aloja devilfruittcg.com. Y es donde aprendí esta capa, que básicamente consistió en romperla y apuntar por qué.
Lo que no está en esta página: direcciones, identificadores de túnel y de dispositivos, subdominios de paneles privados y cualquier cosa con forma de token. Lo interesante es la arquitectura y las decisiones; las coordenadas no, y publicarlas sería justo lo contrario de lo que defiende el montaje.
/ En corto
El demonio del túnel mantiene conexiones salientes hacia Cloudflare, así que el tráfico entra sin que nada escuche en el router y la IP de casa no se publica nunca.
El túnel es para las visitas y solo lleva las webs públicas. Tailscale es para mí y lleva SSH y los paneles. Ninguna puede hacer el trabajo de la otra por accidente.
Cada ruta del proxy y del túnel apunta a un nombre de contenedor. Las IPs cambian en cuanto recreas algo; los nombres no. Dos de las nueve incidencias fueron exactamente esto.
El disco de datos se monta por UUID del sistema de archivos y no por ruta de dispositivo, porque añadir un segundo disco puede reordenar qué significa /dev/sda.
Cada servicio es un Docker Compose, y el razonamiento vive en un runbook en la propia máquina. Rehacerlo es un checkout y un compose up, no una excavación arqueológica.
Síntoma, causa, solución, lección. Escritas en el momento y no reconstruidas después, porque la lección es la parte que sobrevive.
/ 01 · Acceso
Lo obvio para llegar a un servidor de casa desde fuera es abrir puertos en el router. No lo hice, por tres razones: la línea puede estar detrás de CGNAT, con lo cual quizá ni se puede; un puerto abierto es una invitación permanente; y separar el tráfico público de mi propio acceso hace que un error en un lado no pueda exponer el otro.
| Túnel de Cloudflare | Tailscale | |
|---|---|---|
| Para quién | Cualquiera que visite las webs públicas | Solo mis dispositivos |
| Qué lleva | La web de WordPress y un rank tracker | SSH y todos los paneles de administración |
| Cómo conecta | Conexiones salientes del servidor hacia Cloudflare | Una malla privada entre dispositivos autorizados |
| Qué añade | CDN, WAF y protección DDoS por delante | Que no se publica absolutamente nada |
| Config del router | Ninguna | Ninguna |
La regla que lo hizo funcionar: el contenedor del túnel tiene que estar en la red Docker de cada servicio que expone. Si no, el servicio responde perfectamente dentro de la máquina mientras la URL pública devuelve 502, que es como empezaron dos de las nueve incidencias de abajo.
/ 02 · Qué corre dentro
Todo en Docker, detrás de un proxy inverso que se encarga del enrutado y de los certificados. Público y privado están separados a nivel de red, no confiando en que nadie adivine una URL.
| Servicio | Qué hace | Accesible desde |
|---|---|---|
| WordPress | Sirve devilfruittcg.com, la web de nicho del caso de SEO. | Público |
| MySQL | Su base de datos. Nunca expuesta, accesible solo dentro de la red Docker. | Solo interno |
| Proxy inverso | Enrutado y certificados TLS de todo lo que hay detrás. | Panel privado |
| Rank tracker | Seguimiento de keywords self-hosted para la web de nicho, en su propio subdominio. | Público |
| Servidor de fotos | Fototeca personal en el disco de datos de 1 TB. | Privado |
| Dashboard | Una página con todos los servicios y su estado. | Privado |
| Filtro DNS | DNS y bloqueo de publicidad para toda la casa. | Privado |
| Monitor de uptime | Comprueba cada servicio y avisa cuando uno deja de responder. | Privado |
/ 03 · Incidencias
Esta es la parte que yo querría leer de otra persona. Seguir un tutorial hasta que funciona lo hace cualquiera. Lo que merece la pena enseñar es qué pasó cuando no funcionó, y si la causa se entendió de verdad o solo se esquivó.
| Síntoma | Causa | Lección |
|---|---|---|
| El servidor se apagaba al arrancar | Un conector SATA mal asentado después de montar el disco de datos de 1 TB. | Apagado instantáneo a los segundos de encender es conector o cortocircuito. Deja la carcasa abierta y mira los primeros segundos. |
| 502 en la web pública | WordPress y el proxy inverso estaban en redes Docker distintas y no se veían. | Los contenedores de composes distintos son invisibles entre sí. Hay que declarar la red del otro como externa y unirse a ella. |
| 502 en el rank tracker | El demonio del túnel no estaba en la red Docker de ese servicio y no podía resolver el nombre del contenedor. | El contenedor del túnel tiene que estar en la red de cada servicio que expone. |
| Demasiados redireccionamientos en Safari | Cloudflare en modo Flexible mientras el proxy forzaba HTTPS, que entra en bucle. | Flexible más SSL forzado es un bucle. Full más SSL forzado es lo correcto. |
| Fallaba la petición de certificado | El proxy de Cloudflare bloquea la verificación HTTP de Let's Encrypt. | Con la nube naranja activa, siempre DNS challenge, con un token de API acotado. |
| Error de conexión a la base de datos | Quedó un valor de relleno como contraseña en el compose, y MySQL ya había guardado la primera con la que arrancó. | Cambiar la contraseña en el compose no la cambia en el volumen. Hay que recrear el volumen, y solo cuando es seguro hacerlo. |
| Desapareció el panel de administración | El filtro de DNS cambia su puerto interno en cuanto termina su asistente inicial. | Un panel que desaparece justo al acabar la configuración normalmente ha cambiado de puerto. Mira los logs antes de recrear nada. |
| Host validation failed | El dashboard exige ahora declarar explícitamente los hosts permitidos. | Un error de host u origen en el primer acceso es una comprobación de seguridad, no un fallo. Declara las direcciones que usas de verdad. |
| No se podía abrir el fichero de base de datos | Una carpeta montada la creó root, así que el contenedor no podía escribir en ella. | Un contenedor que no puede escribir en su volumen casi siempre es un problema de permisos en la carpeta del host. |
/ 04 · Qué demuestra
| Afirmación | Evidencia |
|---|---|
| La capa de debajo del CMS | Sistema operativo, discos y fstab, red de Docker, proxy inverso, TLS, DNS y acceso remoto. No un panel gestionado haciendo de infraestructura. |
| Seguridad por arquitectura | Sin puertos abiertos, tráfico público y privado por vías distintas, paneles nunca publicados y base de datos nunca expuesta. |
| Diagnóstico, no parches | Nueve incidencias con la causa identificada en cada una. Los 502 eran red de Docker, el bucle de redirecciones era un modo de SSL, el fallo de escritura eran permisos del host. |
| Está documentado | Un runbook en la máquina y un vault de notas enlazadas, que es la razón de que esta página se haya podido escribir desde registros y no desde la memoria. |
| Es real | devilfruittcg.com se sirve desde ahí, en público, y tiene su propio caso. |
Sigue abierto: confirmar si la línea está detrás de CGNAT, sacar una copia antigua de fotos del SSD del sistema y terminar la conexión con Search Console del rank tracker. Un homelab sin tareas pendientes es un homelab que no usa nadie.
/ Siguiente
Esta es la capa que fui a buscar cuando el diseño dejó de bastarme, y en la que quiero seguir. Si necesitas a alguien que se haga cargo de la máquina además de la web, escríbeme.