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:

 
HTTP-01 → si falla → DNS-01
 

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:

 
¿Está en Cloudflare?
¿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

 
curl -I https://dominio.com
 

Buscar:

 
server: cloudflare
cf-ray:
 

Método 2: IPs conocidas de Cloudflare

 
dig dominio.com +short
 

Comparar con rangos de Cloudflare.


Implementación en PHP

 
function isCloudflare(string $domain): bool
{
$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

 
function isCloudflareProxyActive(string $domain): bool
{
$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:

 
function hasCloudflareAccess(string $domain): bool
{
// 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:

 
function resolveChallengeStrategy(string $domain): string
{
$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

 
Sin Cloudflare → HTTP-01
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

 
function buildTlsPolicy(string $domain): array
{
$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:

 
Cloudflare + proxy activo + sin acceso DNS
 

Puedes:

  • Bloquear el dominio
  • Mostrar error claro al usuario
  • Evitar intentos ACME innecesarios

Ejemplo:

 
if ($strategy === 'unsupported') {
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:

 
journalctl -u caddy | grep challenge
 

Buscar patrones:

  • http-01 challenge failed
  • dns-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.