Files
feadulta/docs/cutover/certificados-letsencrypt-cloudflare.md
T
rafa f6170b70b1 docs: por que no salen los certificados de www y el apex
Documento para que lo pueda leer Inma sin tener que reconstruir la conversacion.

Lo esencial: el lado del servidor esta bien. Traefik intercepta la ruta del reto
en el entrypoint http y no la redirige, y se ve en que ese 404 viene vacio y sin
cabecera server mientras cualquier otra ruta da 302. El problema esta delante, en
el borde de Cloudflare: el reto acaba llegando por HTTPS y lo para un 403.

Se recoge tambien por que dos personas midiendo lo mismo obtuvieron resultados
distintos, que es lo que mas tiempo nos ha costado: Cloudflare devuelve 403 a
nuestras IPs para cualquier hostname de feadulta, y con el User-Agent de Let's
Encrypt deja pasar el bloqueo y entonces aparece el 301. La forma del token no
cambia nada. Conclusion util: el unico testigo que vale es lo que reporta Let's
Encrypt, no nuestros curl.

Y un aviso que puede hacernos perder la tarde: Let's Encrypt permite 5
validaciones fallidas por hostname y hora, www toco el limite a las 11:47 UTC y
cada redeploy quema tres. Reintentar antes de que pase la hora falla por rate
limit aunque la regla este bien puesta, y nos haria creer que no funciona. Va el
procedimiento en orden: primero la regla, luego esperar, luego un solo reintento.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 08:00:45 -04:00

5.9 KiB
Raw Blame History

Los certificados de Let's Encrypt de www y el apex no se emiten

Estado: abierto. Bloqueado a la espera de una regla en Cloudflare, que gestiona Inma. Fecha: 3-ago-2026, después del cutover. Plan maestro: rafa/feadulta#180.

Resumen para quien tenga prisa

Traefik no consigue el certificado de www.feadulta.com ni de feadulta.com. No hay nada roto para los visitantes: Cloudflare presenta su propio certificado, y hacia el origen —con el modo SSL de la zona en Full— acepta el certificado por defecto de Traefik. El único coste es que la zona no puede subir a Full (strict) hasta que se resuelva.

La causa es que el reto HTTP-01 de Let's Encrypt acaba llegando por HTTPS y Cloudflare lo bloquea con un 403. Se arregla con una Configuration Rule en Cloudflare acotada a la ruta del reto.

Lo que dice Let's Encrypt, que es el único testigo que vale

2026-08-03T11:46:54Z  domains [www.feadulta.com]
  invalid authorization: acme: error: 403 :: urn:ietf:params:acme:error:unauthorized ::
  Invalid response from https://www.feadulta.com/.well-known/acme-challenge/YnmnUk3wAqkMGkL8Evv49DQrW1euPfnOgNFoekwLSUc: 403

Ese intento es posterior al cambio de DNS, al arreglo del mu-plugin y a dos redeploys, y lleva un token real de 43 caracteres. Nótese el https: el reto empieza por el puerto 80 y termina en el 443.

Por qué las mediciones desde nuestras máquinas no sirven

⚠️ Cloudflare devuelve 403 a nuestras IPs para cualquier hostname de feadulta — la de Rafa y la del propio Hetzner. Eso contamina cualquier curl. Medido:

petición desde la WSL resultado
token real (43 ch), sin User-Agent de LE 403
token real (43 ch), con User-Agent de LE 301 a https
token de ejemplo corto 403
directorio /.well-known/acme-challenge/ a secas 403

Idéntico en apex, www y wp-nuevo. La forma del token no cambia nada; lo que cambia es el User-Agent. Por eso dos personas midiendo lo mismo obtienen resultados distintos y ninguna de las dos tiene razón: hay que mirar lo que reporta Let's Encrypt.

(Se barajó que Cloudflare exime de "Always Use HTTPS" las URLs de challenge que llevan token. En esta zona no se está aplicando: el intento de las 11:46, con token real, terminó en 403.)

La cadena, medida contra el origen

punto resultado quién responde
Origen puerto 80, ruta del reto 404, 0 bytes, sin cabecera server el handler de ACME de Traefik
Origen puerto 80, cualquier otra ruta 302 a HTTPS Traefik
Origen puerto 443, ruta del reto 404, 99.802 bytes WordPress

O sea que el lado del servidor está bien: Traefik intercepta la ruta del reto en el entrypoint http y no la redirige. La configuración lo confirma:

--certificatesresolvers.letsencrypt.acme.httpchallenge=true
--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=http

El problema está delante, en el borde de Cloudflare, no en Hetzner.

El arreglo

Cloudflare → Rules → Configuration Rules. Una regla acotada a la ruta del reto:

  • Ámbito: *feadulta.com/.well-known/acme-challenge/*
  • Always Use HTTPS → Off — para que el reto se complete por el puerto 80, donde Traefik lo atiende
  • Security Level → Essentially Off (o una regla WAF de tipo Skip para esa ruta)

Es una excepción mínima y acotada: esa ruta no sirve contenido, solo tokens de un solo uso que el propio Traefik genera y valida.

Alternativa de fondo, para más adelante

Pasar Traefik a validación DNS-01 con el token de Cloudflare que ya tenemos (FEA_CF_API_TOKEN, permiso Zone → DNS → Edit). Se salta el problema para siempre porque no depende del HTTP, y es lo recomendado cuando el origen vive detrás de un proxy. Pero toca la configuración del proxy de Coolify entero, no solo feadulta, así que no se hace en caliente ni sin ensayarlo.

⚠️ Límite de Let's Encrypt: no reintentar a lo loco

Let's Encrypt permite 5 validaciones fallidas por hostname y hora. Fallos del 3-ago:

hostname fallos horas (UTC)
www.feadulta.com 5 10:44, 11:37, 11:38, 11:46, 11:47
feadulta.com 3 10:44, 11:38, 11:46
wp-nuevo.feadulta.com 4 10:44 ×2, 11:37, 11:46

www tocó el límite a las 11:47 UTC. Reintentar antes de que pase la hora falla por rate limit aunque la regla esté bien puesta, y hace creer que el arreglo no funciona.

Procedimiento correcto

  1. Inma pone la Configuration Rule.
  2. Esperar a que pase una hora desde el último fallo (para www, a partir de las 12:47 UTC).
  3. Lanzar un solo reintento y mirar el resultado antes de tocar nada más.

El reintento obliga a Traefik a recargar configuración, y eso hoy se consigue redesplegando el servicio:

curl -s -H "Authorization: Bearer $COOLIFY_API_TOKEN" \
  "http://localhost:8000/api/v1/deploy?uuid=r2ssjifwj0r0ghyoqd528uaa&force=false"

(El API de Coolify solo escucha en localhost:8000 del servidor: la llamada va por SSH.) Cuesta unos 7 segundos de corte: se ha medido dos veces, un único 500 y vuelta a 200. Cada redeploy quema tres validaciones, una por hostname.

Comprobar si ya salió

docker exec coolify-proxy cat /traefik/acme.json | grep -oE '"main":"[^"]*feadulta[^"]*"'

Hoy sólo aparecen antiguo.feadulta.com, nuevo.feadulta.com y gitea.feadulta.com.

Cabo suelto ya cerrado por el camino

fea-legacy-redirect.php mandaba /.well-known/ al archivo con un 301, porque manda allí cualquier 404. No era la causa —el reto va por el 80 y ahí ni llega a WordPress— pero estaba mal y era una trampa para el futuro. Arreglado y desplegado: las rutas de protocolo quedan exentas y el resto del redirect sigue igual. Lo encontró el Claude de Inma.

Cuando el certificado salga

Entonces —y sólo entonces— se puede subir la zona a Full (strict), que valida además el certificado del origen. Hacerlo antes rompe el sitio.