+++ title = "Caddy: arquitectura de dos capas y alta de dominios con DNS-01/Route53" description = "Las dos capas de Caddy, cómo agregar un dominio interno con certificado real vía DNS-01/Route53, y los dos gotchas conocidos de propagación." date = 2026-08-19T20:25:10Z authors = ["gen"] slug = "run-20260819-a3b63b-caddy-arquitectura-de-dos-capas-y-alta-de-dominios-con-dns-01-route53" template = "page.html" weight = 26 [taxonomies] categories = ["manuales"] tags = ["caddy", "dns-01", "route53", "tls"] systems = ["caddy"] [extra] id = "RUN-20260819-A3B63B" kind = "manuales" author_id = "gen" author_fingerprint = "SHA256:JhN1Tt4jYqWoJIznDJ6QkMAiOR6HFHr3xLz9aBJqmPg" +++ ## Arquitectura: dos Caddys, dos capas ``` Internet ──HTTPS(443)──> Caddy #1 (mcps-vpn-server, IP pública) │ termina TLS real (Let's Encrypt) │ reenvía HTTP plano por el túnel WireGuard ▼ mcps-hpc-01 (10.8.0.18) │ Caddy #2 (ahí mismo) │ segundo enrutamiento por Host header ┌──────────────┼──────────────┬─────────────┐ ▼ ▼ ▼ ▼ nginx/Apache/gunicorn/Grafana/etc (los backends reales) ``` - Config de Caddy #1: `/etc/caddy/Caddyfile` + `/etc/caddy/tenants/*.caddy`. - Config de Caddy #2: `/mnt/cephfs/hot/caddy/Caddyfile` (montado en el contenedor vía `container-caddy.service`, systemd+podman en `.18`). - `mcps-net` (red podman en `.18`) tiene `dns_enabled: false` — por eso el Caddyfile de ahí usa **IPs fijas**, no nombres de contenedor. Si un contenedor se recrea sin `--ip` fijo, su IP cambia y hay que actualizar el Caddyfile a mano. Ponga siempre `--ip 10.89.0.X` fijo a cualquier contenedor nuevo que Caddy vaya a referenciar ahí. ## Dominios VPN-only vs. públicos Los dominios internos (Cockpit, Zammad, pgAdmin, RStudio, Jupyter, Snipe-IT, etc.) apuntan en Route53 a propósito a `10.8.0.18` (IP privada, solo alcanzable por VPN), nunca a la IP pública. Con eso, `tls internal` (certificado autofirmado) da warning de navegador — el patrón correcto es **DNS-01 vía Route53**, que da certificado real de Let's Encrypt sin necesitar reachability pública. ## Prerrequisitos (infraestructura de una sola vez, ya existe) - Usuario IAM dedicado `caddy-route53-dns01`, con permisos mínimos: `route53:ChangeResourceRecordSets` y `route53:ListResourceRecordSets` sobre la zona `mcps-epcm.org` (`Z05793723RVTGN4VZAAUN`), más `GetChange`/`ListHostedZones*` (globales). Nunca usar las credenciales del usuario `Administrator`. - Imagen Caddy custom con el plugin de Route53 (la oficial no lo trae): ```dockerfile FROM caddy:builder AS builder RUN xcaddy build --with github.com/caddy-dns/route53 FROM caddy:latest COPY --from=builder /usr/bin/caddy /usr/bin/caddy ``` Imagen: `localhost/caddy-route53:latest`. - Credenciales en `/etc/caddy-route53.env` (`root:root`, `600`), inyectadas al contenedor vía `--env AWS_ACCESS_KEY_ID --env AWS_SECRET_ACCESS_KEY` (forma "bare" de podman: toma el valor del entorno del proceso llamador, por eso el `EnvironmentFile=` en el systemd unit). ## Procedimiento: agregar un dominio nuevo a este patrón 1. En Route53, apunte el registro del dominio a `10.8.0.18` (VPN-only) o a la IP pública correspondiente (público). 2. En `/mnt/cephfs/hot/caddy/Caddyfile`, agregue el bloque del sitio con: ```caddy tls { dns route53 { access_key_id {env.AWS_ACCESS_KEY_ID} secret_access_key {env.AWS_SECRET_ACCESS_KEY} } propagation_delay 20s propagation_timeout -1 } ``` 3. `caddy validate` sobre el Caddyfile antes de aplicar. 4. `caddy reload` (no reinicia el proceso, no tira el resto de sitios). ## Por qué `propagation_timeout -1` y `propagation_delay 20s`, no el default La red de `.18` redirige por transparencia todo el tráfico DNS al resolver `132.248.204.1` (red UNAM), sin importar qué servidor le pidas — confirmado con `nslookup ... 8.8.8.8` desde dentro de un contenedor, devolviendo `NXDOMAIN` mientras el registro sí existía y propagaba bien visto desde `mcps-vpn-server`. El chequeo de propagación *propio* de Caddy (usando ese resolver secuestrado/con caché negativo viejo) se queda colgado indefinidamente. - `propagation_timeout -1`: Caddy no verifica localmente, deja que Let's Encrypt valide directo desde su red. - `propagation_delay 20s`: espera fija para dar tiempo real a Route53, evita condición de carrera si el registro aún no propagó del todo. Nota: esto **no** es una ruta de red real hacia la UNAM, solo el DNS configurado en el router — la salida real a internet de `.18` es Telmex (única default route). No hay forma de salir con IP de la UNAM desde esta infraestructura. ## Verificación - `curl -v https://.mcps-epcm.org` muestra `SSL certificate verify ok` con emisor Let's Encrypt, no un certificado autofirmado. - El sitio responde sin advertencia de certificado en el navegador (dentro de la VPN, si es VPN-only). ## Solución de problemas - **`Duplicate Resource Record` al reintentar DNS-01**: si se mata/reinicia Caddy a medio intento, puede quedar un registro `_acme-challenge.` huérfano en Route53 que choca con el siguiente intento. Bórrelo a mano: ```bash aws route53 list-resource-record-sets --hosted-zone-id Z05793723RVTGN4VZAAUN \ --query "ResourceRecordSets[?Name=='_acme-challenge..mcps-epcm.org.']" # luego change-resource-record-sets con Action=DELETE y el mismo valor ``` - **El challenge se queda colgado indefinidamente**: confirme que el bloque `tls` tiene `propagation_timeout -1` — sin eso, Caddy intenta verificar la propagación usando el resolver secuestrado de la red de `.18` y nunca termina. - **Un contenedor backend cambia de IP y el sitio deja de responder**: confirme que el contenedor se recreó con `--ip` fijo; si no, actualice el Caddyfile a la IP nueva.