Problema
Muchas instalaciones de homelab o entornos de producción ligera necesitan certificados TLS para dominios y sub‑dominios, a menudo con soporte wildcard. El método DNS‑01 de ACME es el único que permite wildcard, pero requiere que el cliente pueda crear y borrar registros TXT en la zona DNS. La práctica más extendida es dar a Certbot (o a un proxy como Traefik) acceso mediante una API del proveedor DNS. Eso implica almacenar una clave API en el host, lo que genera dos problemas recurrentes:
- Superficie de ataque ampliada – cualquier proceso con acceso a la clave puede modificar la zona completa.
- Mantenimiento frágil – los contenedores que gestionan la API y el cliente suelen estar acoplados; una actualización rompe la comunicación o los volúmenes se desincronizan.
El patrón que se repite es “necesito DNS‑01 sin exponer credenciales y con la mínima cantidad de componentes”. La solución debe ser portable, fácil de desplegar en Docker (o en una VM) y capaz de delegar únicamente el sub‑dominio _acme-challenge mediante un registro CNAME.
Causa
Los fallos habituales provienen de tres áreas:
- Dependencia de API del registrador – la mayoría de los proveedores solo exponen una API REST; si el contenedor que la consume se reinicia, la clave puede quedar expuesta en logs o volúmenes temporales.
- Arquitectura multi‑container – separar acme‑dns, la UI y Certbot en contenedores obliga a crear redes internas, montar volúmenes compartidos y exponer puertos internos. Cada capa añade latencia y puntos de falla.
- Gestión de puerto 53 – el servicio DNS necesita escuchar en el puerto 53/UDP (y a veces TCP). En entornos donde otro DNS local ya usa ese puerto, el despliegue colisiona y el contenedor no arranca.
Solución
Una arquitectura monolítica dentro de un único contenedor elimina la mayor parte de la complejidad. El contenedor ejecuta:
- acme‑dns escuchando en
0.0.0.0:53/udp(y TCP para consultas de zona). - API HTTP que permite registrar dominios y generar los registros TXT necesarios.
- UI ligera (por ejemplo Nuxt) para crear cuentas y visualizar los valores de desafío.
- Certbot (o cualquier cliente ACME) configurado para usar el endpoint HTTP de acme‑dns como “authenticator”.
Paso a paso general
-
Crear zona delegada
En el panel del registrador crea un registro CNAME llamado_acme-challenge.tu-dominio.comque apunte aacme-dns.tu-dominio.com.acme-dns.tu-dominio.comserá la zona gestionada por el contenedor. -
Construir la imagen
Usa un Dockerfile que combineacme-dns,certboty la UI. La mayoría de los proyectos open‑source ya publican una imagen lista; si prefieres compilar, parte degolang:alpinepara acme‑dns y añadecertbotdesdepython:alpine. -
Ejecutar el contenedor
- Mapear el puerto 53 tanto UDP como TCP.
- Montar un volumen persistente para la base SQLite de acme‑dns y para los certificados de Certbot.
- Definir la variable
ACME_DNS_API_URLpara que Certbot apunte al endpoint interno (http://localhost:8080/acme-dns).
-
Registrar la zona en acme‑dns
La UI o la API permite crear una “account” y asignarle la zona delegada. El cliente ACME recibirá automáticamente los valores TXT que acme‑dns sirve. -
Solicitar / renovar certificados
Ejecuta Certbot con el plugin--manualapuntando al endpoint HTTP de acme‑dns. La renovación puede programarse concrondentro del mismo contenedor.
Ventajas de la solución única
- Sin claves API externas – solo el registro CNAME es necesario en el registrador.
- Persistencia simple – un único volumen contiene tanto la base de datos de desafíos como los certificados.
- Actualizaciones atómicas – al actualizar la imagen, todo el stack se reinicia de forma coherente.
- Portabilidad – funciona en cualquier host Docker, incluso en Synology, Raspberry Pi o una VM en la nube.
Cuándo aplicar esta solución
Escenarios ideales
- Homelabs o servidores personales que gestionan dominios propios y necesitan wildcard.
- Entornos donde el registrador no ofrece una API segura o la política de la empresa prohíbe almacenar tokens.
- Deployments con recursos limitados que prefieren un solo contenedor en lugar de varios micro‑servicios.
Señales de que la solución encaja
- Necesitas renovar certificados automáticamente sin intervención manual.
- El puerto 53 está libre o puedes redirigir tráfico DNS a la máquina que ejecuta el contenedor.
- Quieres evitar que el proceso de reverse proxy (Traefik, Caddy) tenga permisos de escritura en la zona DNS.
Casos donde no aplica
- Infraestructuras que usan DNS interno con políticas de zona estrictas y no permiten delegar sub‑zonas.
- Entornos que requieren alta disponibilidad de DNS y no pueden depender de un solo contenedor para responder consultas.
- Situaciones donde el registrador exige autenticación basada en API para cada desafío (p.ej., Cloudflare con rate‑limit estricto).
Código
docker run -d \
--name dns01-stack \
--restart unless-stopped \
-p 53:53/udp -p 53:53/tcp \
-v /srv/dns01/data:/data \
-e ACME_DNS_API_URL=http://localhost:8080/acme-dns \
ghcr.io/usuario/dns01-stack:latest
/srv/dns01/datacontendráacme-dns.dby los certificados de Certbot.- La variable
ACME_DNS_API_URLsolo es necesaria si la imagen no asumelocalhost.
Para solicitar un certificado wildcard:
docker exec dns01-stack certbot certonly \
--manual \
--preferred-challenges dns \
--manual-auth-hook "/usr/local/bin/acme-dns-auth.sh" \
--manual-cleanup-hook "/usr/local/bin/acme-dns-cleanup.sh" \
-d "*.example.com" -d "example.com"
Los scripts acme-dns-auth.sh y acme-dns-cleanup.sh están preinstalados en la imagen y utilizan la API interna.
## Verificación
1. **Comprobar que acme‑dns responde**
```bash
dig @127.0.0.1 _acme-challenge.example.com TXT +short
Debería devolver el valor generado por la API.
-
Validar el certificado
openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null | openssl x509 -noout -datesLa fecha de expiración debe coincidir con la renovada por Certbot.
-
Revisar logs del contenedor
docker logs dns01-stack --tail 20Busca líneas que indiquen “challenge completed” y “certificate saved”.
-
Simular una renovación
Forzar la renovación concertbot renew --dry-rundentro del contenedor y confirmar que el proceso completa sin errores.
Notas adicionales
- Firewall – Asegúrate de que el host permite tráfico UDP/TCP en el puerto 53 desde la red donde se ejecuta Let’s Encrypt (generalmente internet). Un
iptables -A INPUT -p udp --dport 53 -j ACCEPTsuele ser suficiente. - TTL bajo – Configura un TTL corto (5‑10 min) para el registro CNAME delegante; esto reduce el tiempo de propagación cuando cambias de contenedor o actualizas la base de datos.
- Backup de la base SQLite – Copia periódicamente
/srv/dns01/data/acme-dns.db. Sin él, perderás la asociación entre dominios y tokens, obligando a recrear los registros CNAME. - Escalado – Si en el futuro necesitas alta disponibilidad, puedes replicar la base SQLite mediante un NFS compartido y lanzar varios contenedores detrás de un balanceador de carga que distribuya consultas DNS (aunque la especificación de ACME espera que el desafío sea servido por un único servidor).
Con este enfoque, obtienes certificados wildcard sin exponer claves API, mantienes la infraestructura simple y reduces la superficie de fallo a un único contenedor Docker gestionable.