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>
This commit is contained in:
@@ -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.
|
||||
Reference in New Issue
Block a user