From f6170b70b114e874d1743e33f76547c6cf525d22 Mon Sep 17 00:00:00 2001 From: rafa Date: Mon, 3 Aug 2026 08:00:45 -0400 Subject: [PATCH] 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 --- .../certificados-letsencrypt-cloudflare.md | 133 ++++++++++++++++++ 1 file changed, 133 insertions(+) create mode 100644 docs/cutover/certificados-letsencrypt-cloudflare.md diff --git a/docs/cutover/certificados-letsencrypt-cloudflare.md b/docs/cutover/certificados-letsencrypt-cloudflare.md new file mode 100644 index 0000000..8edeeda --- /dev/null +++ b/docs/cutover/certificados-letsencrypt-cloudflare.md @@ -0,0 +1,133 @@ +# 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.