En corto
Delega una zona pequeña a la máquina donde correrá acme-dns (registros en tu DNS real):
1 2auth.example.com. NS acme.example.com. acme.example.com. A 203.0.113.10acme-dns en esa máquina (
/etc/acme-dns/config.cfg), escuchando DNS en el 53 y la API en el 443:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15[general] listen = "0.0.0.0:53" protocol = "both" domain = "auth.example.com" nsname = "acme.example.com" nsadmin = "hostmaster.example.com" records = [ "auth.example.com. A 203.0.113.10", "auth.example.com. NS acme.example.com.", ] [api] ip = "0.0.0.0" port = "443" tls = "letsencrypt" disable_registration = falseRegistra un cliente y crea el CNAME:
1 2 3curl -s -X POST https://acme.example.com/register -H 'Content-Type: application/json' \ -d '{"allowfrom": ["203.0.113.0/24"]}' # {"username":"eabcdb41-…","password":"pbAX…","fulldomain":"d420c923-….auth.example.com","subdomain":"d420c923-…","allowfrom":["203.0.113.0/24"]}1_acme-challenge.example.com. CNAME d420c923-….auth.example.com.certbot con el hook:
1 2 3 4 5curl -fsSL -o /etc/letsencrypt/acme-dns-auth.py https://raw.githubusercontent.com/joohoi/acme-dns-certbot-joohoi/master/acme-dns-auth.py chmod +x /etc/letsencrypt/acme-dns-auth.py sed -i 's|^#!/usr/bin/env python$|#!/usr/bin/env python3|; s|^ACMEDNS_URL = .*|ACMEDNS_URL = "https://acme.example.com"|' /etc/letsencrypt/acme-dns-auth.py certbot certonly --manual --manual-auth-hook /etc/letsencrypt/acme-dns-auth.py \ --preferred-challenges dns --debug-challenges -d '*.example.com' -d example.comLa primera vez el hook se registra solo, guarda las credenciales en
/etc/letsencrypt/acmedns.jsony se para para que crees el CNAME que imprime; Enter, y valida. Las renovaciones (certbot renew) ya no preguntan nada.
Por qué
DNS-01 es el único reto que da wildcards y el único que funciona para máquinas sin puerto 80 público. El precio habitual es un token de API de tu proveedor DNS en cada servidor que pide certificados; ese token normalmente puede cambiar cualquier registro de la zona, y vive en texto plano en /etc/letsencrypt.
acme-dns le da la vuelta: es un servidor DNS autoritativo de una zona propia (auth.example.com) que solo sabe servir registros TXT bajo subdominios aleatorios, con una API para actualizarlos. Como _acme-challenge.example.com es un CNAME hacia uno de esos subdominios, Let’s Encrypt sigue la cadena y valida el TXT allí. Las credenciales de cada cliente solo sirven para su subdominio; robadas, lo peor que pasa es que alguien pida un certificado para ese nombre, no que reescriba tu zona.
cert-manager
El mismo esquema, con las credenciales en un Secret:
| |
| |
| |
La clave del JSON es el dominio del certificado; para *.example.com y example.com vale la misma entrada (el CNAME _acme-challenge.example.com cubre ambos). Cada dominio distinto necesita su registro y su CNAME.
Trampas
allowfromcon la IP que ve acme-dns: la pública de salida del servidor o del clúster, no la del pod. Si falla el/updatecon 401, es esto o la contraseña.- TTL del CNAME: ponlo bajo (60–300 s) la primera vez; con un TTL de una hora, un error en el CNAME tarda una hora en corregirse a ojos de Let’s Encrypt. El TXT lo sirve acme-dns con TTL 1.
- Puerto 53 de verdad: acme-dns tiene que ser alcanzable por UDP y TCP desde Internet, y ser el NS de
auth.example.comen tu zona padre. Sin el registro NS, la delegación no existe y la validación devuelve NXDOMAIN. - Un registro por dominio, no por servidor: si tres servidores piden certificados para
example.com, comparten el mismo registro y el mismoacmedns.json, o cada uno registra el suyo y el CNAME solo puede apuntar a uno. Comparte las credenciales. --manualrenueva bien porque el hook está guardado en/etc/letsencrypt/renewal/<dominio>.conf; lo que no puedes es cambiar el hook sin volver a ejecutarcertonly.- Versión: acme-dns 2.0.2 es de febrero de 2025 y el proyecto sigue activo; el hook de certbot no ha cambiado en años y funciona con Python 3 tras corregir el shebang.
Referencias: acme-dns, acme-dns-certbot-joohoi y ACMEDNS en cert-manager.
Ver también
- cert-manager: el reto HTTP-01 devuelve 308 — cuando HTTP-01 es suficiente y no lo es.
- HAProxy en pfSense con ACME y Cloudflare — DNS-01 con el token del proveedor, el caso que acme-dns evita.
- DKIM de 2048 bits en un TXT partido — otro registro DNS que se rompe por detalles.