# 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: ```bash 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ó ```bash 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.