La regla que me impuse desde el día uno
Cuando empecé a montar servicios sobre el Debian 13 de este servidor, me puse una regla simple: ni un puerto abierto al router. Nada de reenviar el 443 o el 22 hacia la casa. Todo el tráfico externo entra por un único cloudflared corriendo en Docker, que expone cada servicio en su propio subdominio de ulloa.cc. Hacia afuera, mi IP real no existe. No hay nada que escanear, porque no hay nada escuchando.
Esa decisión resuelve de raíz una categoría entera de problemas: no hay superficie de ataque directa contra el router ni contra la IP residencial. Pero resolver esa capa no significa que el resto quede resuelto solo, y ahí es donde aprendí algo que no esperaba.
Fail2ban, actualizaciones automáticas, y la falsa sensación de estar cubierto
Con el túnel andando, seguí armando las capas de seguridad "de manual": fail2ban vigilando SSH con ban progresivo real (no solo bloquear, sino ir aumentando el tiempo de castigo con cada reincidencia), y actualizaciones de seguridad automáticas configuradas para no forzar reinicios sorpresa a media noche. Con eso, en cualquier servidor tradicional expuesto a internet, ya tienes una base sólida.
El problema es que ese modelo mental viene de una época donde el atacante le habla directo a tu máquina. Con un túnel de Cloudflare de por medio, eso deja de ser cierto.
El descubrimiento: un ban de firewall no sirve de nada contra tráfico que llega por el túnel
La primera vez que revisé en detalle cómo llegaba el tráfico al servidor a través de cloudflared, until entendí el problema: todas las conexiones entrantes llegan desde las IPs de borde de Cloudflare, no desde la IP real del cliente. Si fail2ban banea una IP a nivel de iptables, está baneando... a Cloudflare. La próxima petición de ese mismo atacante, sea legítimo o no, vuelve a pasar sin problema, porque técnicamente viene de un origen distinto en cada intento desde la perspectiva del kernel.
Es un caso concreto de algo que en DevOps corporativo se repite constantemente: cuando pones cualquier proxy, balanceador o CDN delante de tu infraestructura, todas las reglas de seguridad basadas en "IP de origen" a nivel de red dejan de significar lo que significaban. No es un bug de Cloudflare ni de fail2ban; es que estaba aplicando una defensa de la capa equivocada.
Bajar la protección a donde realmente vive el tráfico
La solución no es abandonar fail2ban, sino moverlo de capa: en vez de vigilar conexiones a nivel de red, hay que vigilar intentos de autenticación a nivel de aplicación. Eso significa loggear los intentos fallidos de login de cada servicio (Home Assistant, cualquier panel propio) con la cabecera real del cliente que Cloudflare sí preserva (CF-Connecting-IP), y que sea esa capa aplicativa la que decida bloquear o exigir reintentos, no el firewall del sistema operativo.
Ahí fue donde usar un LLM me ahorró tiempo de verdad, no como "magia de IA" sino como lo que es: ayuda para escribir rápido un filtro regex de fail2ban personalizado que lea el formato específico de log de cada aplicación y extraiga la IP correcta del header en vez de la IP de conexión TCP. Es el tipo de tarea mecánica y tediosa donde perder veinte minutos ajustando una expresión regular no aporta nada, y donde tener un asistente que entiende el formato de log y itera contigo sí acelera el trabajo real.
La lección que se lleva a cualquier infraestructura
Lo que aprendí acá con un servidor casero es exactamente lo mismo que aplica en cualquier arquitectura corporativa con proxy inverso, CDN o API gateway delante: cada capa que agregas para exponer servicios de forma segura también cambia dónde vive la verdad sobre "quién está haciendo esta petición". Si tu estrategia de seguridad no se actualiza con esa capa, terminas con controles que parecen activos en el dashboard pero que no están deteniendo nada.
Cero puertos abiertos sigue siendo, para mí, la decisión correcta. Solo que entendí que "sin puertos abiertos" no es sinónimo de "sin trabajo de seguridad por hacer" — es sinónimo de que ese trabajo se mueve a otro lugar, y hay que ir a buscarlo ahí.