Detección automática de Cloudflare y selección dinámica de challenge en Caddy (arquitectura avanzada)
Cuando gestionas múltiples dominios en un entorno dinámico (SaaS, hosting, paneles), no basta con definir HTTP-01 y DNS-01. El verdadero salto de calidad está en decidir automáticamente qué método usar antes de que falle.
En este artículo se muestra cómo construir un sistema que:
- Detecta si un dominio está detrás de Cloudflare
- Determina si tienes acceso al DNS
- Ajusta dinámicamente la estrategia de validación en Caddy
Problema a resolver
El enfoque tradicional:
Problema:
- Hace perder tiempo en intentos fallidos
- Genera ruido en logs
- Puede provocar rate limits en Let's Encrypt
Objetivo
Tomar decisiones antes de emitir el certificado:
¿Tengo acceso al DNS?
¿Está el proxy activo?
→ Elegir challenge correcto directamente
Paso 1: detectar si el dominio usa Cloudflare
Método 1: cabeceras HTTP
Buscar:
cf-ray:
Método 2: IPs conocidas de Cloudflare
Comparar con rangos de Cloudflare.
Implementación en PHP
{
$headers = get_headers("https://$domain", 1);
if (!$headers) {
return false;
}
foreach ($headers as $key => $value) {
if (stripos($key, 'cf-ray') !== false) {
return true;
}
}
return false;
}
Paso 2: detectar si el proxy está activo
Cloudflare puede estar:
- DNS only → OK para HTTP-01
- Proxy activo → rompe HTTP-01
Heurística simple
{
$headers = get_headers("http://$domain", 1);
if (!$headers) return false;
return isset($headers['CF-RAY']) || isset($headers['cf-ray']);
}
Paso 3: comprobar si tienes acceso DNS
Esto depende de tu sistema:
{
// Ejemplo: comprobar si está en tu base de datos
return Domain::where('domain', $domain)
->whereNotNull('cloudflare_account_id')
->exists();
}
Paso 4: motor de decisión
Aquí está la clave:
{
$isCf = isCloudflare($domain);
$proxy = isCloudflareProxyActive($domain);
$hasDns = hasCloudflareAccess($domain);
if (!$isCf) {
return 'http-01';
}
if ($isCf && !$proxy) {
return 'http-01';
}
if ($isCf && $proxy && $hasDns) {
return 'dns-01';
}
return 'unsupported';
}
Interpretación
Cloudflare sin proxy → HTTP-01
Cloudflare con proxy + acceso → DNS-01
Cloudflare con proxy sin acceso → ERROR
Paso 5: aplicar políticas dinámicas en Caddy
En lugar de un catch-all fijo, puedes generar políticas dinámicamente.
Ejemplo
{
$strategy = resolveChallengeStrategy($domain);
if ($strategy === 'http-01') {
return [
'subjects' => [$domain],
'issuers' => [
['module' => 'acme']
]
];
}
if ($strategy === 'dns-01') {
return [
'subjects' => [$domain],
'issuers' => [
[
'module' => 'acme',
'challenges' => [
'dns' => [
'provider' => [
'name' => 'cloudflare',
'api_token' => getTokenForDomain($domain)
]
]
]
]
]
];
}
throw new Exception("No valid challenge strategy for $domain");
}
Paso 6: prevenir errores antes de que ocurran
Cuando detectas:
Puedes:
- Bloquear el dominio
- Mostrar error claro al usuario
- Evitar intentos ACME innecesarios
Ejemplo:
throw new Exception(
"El dominio está detrás de Cloudflare con proxy activo y no hay acceso DNS. " .
"Desactiva el proxy o proporciona API token."
);
}
Bonus: detección pasiva en logs
Puedes identificar problemas automáticamente analizando logs de Caddy:
Buscar patrones:
http-01 challenge faileddns-01 challenge failed
Ventajas de este enfoque
- Menos errores en producción
- Menos intentos fallidos (evita rate limits)
- Mejor experiencia para clientes
- Arquitectura más predecible
Limitaciones
- No puedes evitar el caso:
- Cloudflare externo
- Proxy activo
- Sin acceso DNS
Esto es una limitación del protocolo ACME, no del servidor.
Aplicable a otros servidores
Este enfoque también aplica a:
- Nginx con Certbot
- Apache HTTP Server
La diferencia es que Caddy facilita la automatización.
Conclusión
La diferencia entre un sistema que “funciona” y uno robusto está en anticiparse a los fallos.
No basta con tener HTTP-01 y DNS-01 configurados. Es necesario:
- Detectar el entorno
- Entender quién controla el DNS
- Adaptar la estrategia dinámicamente
Esto convierte un sistema frágil en uno preparado para producción real en entornos multi-tenant.