··
Mi VPS llevaba año y medio respondiendo directamente a internet. Una tarde decidí ponerlo entero detrás de Cloudflare. Esta es la primera entrega de la serie en la que cuento por qué, qué capas monté y en qué orden.

Mi VPS llevaba año y medio respondiendo directamente a internet. Traefik atendiendo el puerto 443, Let's Encrypt validando por HTTP-01 en el puerto 80, y unas cuantas decenas de aplicaciones colgadas de dos zonas DNS. Funcionaba bien, pero cada vez que abría los logs de acceso veía lo de siempre, ráfagas de peticiones a /wp-admin, /.env, /.git/config, intentos de login a /xmlrpc.php y bots probando CVEs de WordPress en proyectos donde no hay una sola línea de PHP.
Decidí poner Cloudflare delante de todo. No solo como CDN, sino como proxy real, con Authenticated Origin Pulls activo y un firewall en el VPS que rechaza cualquier conexión que no venga de los rangos oficiales de CF. La idea es sencilla, mi origen no debería hablar con nadie que no sea Cloudflare.
Esta es la primera entrega de una serie de siete posts en la que cuento toda la migración. Cómo cambié los certificados a DNS-01, cómo configuré AOP, cómo blindé los puertos del kernel, cómo monté un monitor que reinyecta automáticamente el token cuando Dokploy me lo borra, qué reglas WAF aplico en el plan Free y qué bug silencioso descubrí en mis propias apps al meter Cloudflare en medio.
Antes de la migración el VPS quedaba así. Una IP pública, los puertos 80 y 443 abiertos a todo internet y Traefik gestionando los certificados con Let's Encrypt vía HTTP-01. Cualquiera podía conectarse directamente al hostname o a la IP, y el log de acceso era una crónica diaria del ruido de internet.
Las aplicaciones tenían su propia capa de protección. Rate limit por IP, CrowdSec con su bouncer en Traefik, headers de seguridad razonables, autenticación básica donde tocaba. Pero todas esas defensas asumían un modelo donde el atacante llega hasta el origen. Lo que faltaba era una primera capa que filtrara el tráfico mucho antes, idealmente fuera de mi infraestructura, y que ocultara la IP del VPS.
Mi modelo de amenazas no es paranoico. No espero un ataque dirigido sofisticado, espero el ruido continuo del internet abierto y un eventual pico de tráfico que sature la línea del proveedor. Mis objetivos al meter Cloudflare en medio fueron cuatro.
Esconder la IP del origen para que los scanners no puedan saltarse Cloudflare ni montar un DDoS volumétrico contra el VPS.
Filtrar tráfico hostil antes de que llegue a mi VPS, con reglas WAF que bloqueen rutas de scanners conocidos, geoblock de países desde los que nunca recibo tráfico legítimo y rate limiting en endpoints sensibles.
Autenticar el tráfico al origen con un cert cliente firmado por la CA de Cloudflare. Si por cualquier motivo alguien descubre la IP del VPS y la apunta con un Host header válido, Traefik le tira la conexión en el handshake TLS porque no presenta el cert cliente.
Mantener todo dentro del plan Free de Cloudflare. Sin pagar Pro ni ACM, asumiendo las limitaciones del Free como restricciones de diseño.
Lo que no quería era acoplarme a Cloudflare hasta el punto de no poder salir. Las apps siguen sin saber que están detrás de CF, los certificados siguen siendo Let's Encrypt en mi VPS, los datos siguen en mi infraestructura. Cloudflare es un proxy inteligente delante, no un sustituto del stack.
El resultado es una arquitectura por capas, cada una con un propósito claro y todas trabajando en conjunto.
Internet
|
v
[Cloudflare edge] WAF Free, Universal SSL, geoblock
|
v
[Firewall del kernel] iptables + ipset whitelist de IPs CF en 80/443
|
v
[Traefik con AOP] tls.options exige cert cliente firmado por CA de CF
|
v
[Apps con rate-limit] cada app con su propia logica anti-abusoEl primer filtro vive fuera de mi VPS. Cloudflare presenta el certificado público al visitante, valida el host, aplica las reglas WAF (geoblock, paths de scanner, rate limit) y reenvía la petición al origen. Cualquier petición bloqueada en esta capa nunca toca mi servidor, ni siquiera consume CPU para el handshake TLS.
Aunque CF sea el camino legítimo, alguien podría descubrir la IP del VPS y conectar directamente. La segunda capa es un script systemd que descarga la lista oficial de rangos de Cloudflare cada noche y la mete en un ipset. Las reglas de iptables en la chain DOCKER-USER aceptan tráfico en 80 y 443 solo desde esos rangos, todo lo demás se descarta. El puerto 3000 de la UI de Dokploy también queda cerrado al exterior pero abierto entre contenedores.
Si el firewall fallase o lo deshabilitara por error, queda Traefik. Configuré una tls.options llamada cloudflare-aop con clientAuthType: RequireAndVerifyClientCert y la CA pública de Cloudflare como fichero confiable. Cualquier handshake TLS que no presente un cert cliente firmado por esa CA termina en error de SSL antes de llegar a la aplicación. Solo Cloudflare presenta ese cert, así que solo Cloudflare puede iniciar TLS contra mis routers.
Cada aplicación sigue teniendo su propia lógica anti-abuso. Rate limit por IP, owner verification por hash, integridad de logs con HMAC, lo que cada caso necesite. La diferencia es que ahora la IP del cliente la lee de la cabecera cf-connecting-ip, no de x-real-ip. Esto último tiene su propia historia y le he dedicado el último post de la serie, porque romperlo en silencio fue uno de los efectos colaterales más sutiles de meter Cloudflare en medio.
Si el firewall cierra el puerto 80 a todo lo que no sea Cloudflare, Let's Encrypt no puede validar dominios por HTTP-01. La solución es cambiar el resolver de Traefik a DNS-01, que valida creando un registro TXT en la zona DNS vía la API de Cloudflare. El token se inyecta como variable de entorno en el contenedor de Traefik y todos los routers se mueven al nuevo resolver.
Esto trae un detalle operacional, si Dokploy se actualiza, recrea el contenedor de dokploy-traefik sin la variable de entorno y las renovaciones empiezan a fallar en silencio. La detección manual sería al cabo de 60 a 90 días, cuando los certs caducaran. Para evitarlo, monté un timer systemd que revisa cada hora si el token sigue dentro del contenedor y, si no está, lo reinyecta automáticamente leyendo de un fichero local y avisándome por ntfy. Es una piecita pequeña pero cubre uno de los modos de fallo más realistas del setup.
En las siguientes entregas voy entrando en cada pieza con el detalle suficiente para que se pueda replicar.
Este post, motivación y arquitectura.
DNS-01 y AOP en Traefik, los dos cambios principales en el reverse proxy y el orden correcto cuando se hacen en la misma sesión.
Firewall por IPs de Cloudflare, el script systemd, los ipset y la lección de los DROPs sin -i ens3 que me rompieron el tráfico entre contenedores.
Monitor del token de Cloudflare, el timer horario que detecta cuando Dokploy borra el env var y lo reinyecta solo.
WAF, cache y hardening en Cloudflare Free, las recetas concretas que usé sin pagar plan superior.
El bug silencioso de cf-connecting-ip, qué se rompe en tus apps cuando metes CF delante de Traefik y cómo arreglarlo en cualquier proyecto Node.
Cloudflare Tunnel y Access para sacar los paneles admin de Internet, una entrega posterior donde saco Dokploy, Infisical y Umami del DNS público y los meto detrás de identidad.
Aparte de los seis posts de la serie, llevo unas semanas valorando llevar el estado agregado de toda esta infra a una pantalla de e-ink al lado del monitor, alimentada por los mismos timers que ya avisan por Telegram. Es la cara visible que todavía me falta.
El plan no es vender Cloudflare, es contar el camino de quien tenía un VPS expuesto y decidió ponerle una capa más por delante con criterios de ingeniero de fin de semana, sin presupuesto y con todo el código y los runbooks bajo control. Si el día de mañana decido salir de CF, el rollback de cada capa está documentado.

Jose, autor del blog
QA Engineer. Escribo en voz alta sobre automatización, IA y arquitectura de software. Si algo te ha servido, escríbeme y cuéntamelo.
¿Qué te ha parecido? ¿Qué añadirías? Cada comentario afina la siguiente entrada.
Si esto te ha gustado

ScamDetector combina inteligencia artificial, búsqueda de reputación de teléfonos y escaneo de URLs para ayudarte a identificar estafas digitales. Sin registro, sin datos almacenados.

Repaso completo de las medidas de seguridad que puedes aplicar a un VPS Linux: desde CrowdSec y el firewall hasta el hardening del kernel, pasando por SSH, Docker y las actualizaciones automáticas.

Nuestros posts viven en una base de datos SQLite. Si alguien accede a ella, puede cambiar cualquier artículo sin dejar rastro. Construimos un verificador externo con hashes SHA-256 y firma Ed25519 que vigila la integridad desde un segundo servidor.