Certificado wildcard con Let's Encrypt mediante la validación DNS

Publicado el 15 min de lectura

Un certificado wildcard no se puede validar a través del servidor web, va obligatoriamente por un registro TXT en el DNS. Esta guía muestra la vía manual y la automática en Debian 13, Debian 12, Ubuntu 24.04 y Ubuntu 22.04, además de los errores en los que realmente se atasca el proceso.

Un certificado wildcard cubre todos los nombres de un mismo nivel: shop.MiDominio.es, mail.MiDominio.es, cliente-4711.MiDominio.es, incluidos los que hoy todavía no existen. Precisamente por eso el camino habitual, el que pasa por el servidor web, aquí ya no sirve. Esta guía muestra las dos vías viables, la manual y la automática, y presta especial atención a los puntos en los que la cosa se tuerce en la práctica.

Todo lo que sigue está comprobado en Debian 13, Debian 12, Ubuntu 24.04 LTS y Ubuntu 22.04 LTS. Los cuatro sistemas se diferencian en Certbot bastante más de lo que admiten la mayoría de las guías, así que más abajo encontrarás una tabla al respecto.

Por qué un certificado wildcard solo funciona por DNS

Let's Encrypt conoce tres métodos de validación. Dos de ellos quedan descartados para los wildcards:

  • HTTP-01 deposita un archivo en http://name/.well-known/acme-challenge/token. Pero para *.MiDominio.es no existe ningún nombre único bajo el que ese archivo pudiera estar. La autoridad de certificación tendría que consultar una cantidad infinita de nombres de host.
  • TLS-ALPN-01 tiene el mismo problema, también valida un host concreto en el puerto 443.
  • DNS-01 comprueba un registro TXT en _acme-challenge.MiDominio.es. Quien puede crear ese registro controla la zona y, con ella, cualquier nombre por debajo. Es la única prueba que encaja con un asterisco.

En la práctica esto significa que --apache, --nginx, --webroot y --standalone no se pueden usar para wildcards. Quien lo intenta igualmente recibe este mensaje:

Client with the currently selected authenticator does not support any
combination of challenges that will satisfy the CA. You may need to use an
authenticator plugin that can do challenges over DNS.

No es un fallo de tu configuración, sino la respuesta correcta a una petición imposible. Para el caso normal, con unos pocos nombres fijos, la vía del servidor web sigue siendo la adecuada, y la describimos en el artículo sobre el certificado SSL gratuito con Certbot.

Un segundo punto que casi todas las guías se callan: un certificado wildcard cubre solo los nombres de un nivel por debajo. *.MiDominio.es vale para shop.MiDominio.es, pero ni para MiDominio.es en sí ni para a.b.MiDominio.es. El dominio raíz tienes que solicitarlo aparte, y eso tiene consecuencias para el registro TXT, como verás más abajo.

Requisitos y disponibilidad de paquetes según la distribución

Necesitas acceso root por SSH, un dominio cuya zona administres y Certbot. Para la emisión no hace falta un servidor web en marcha, ni que el puerto 80 esté abierto. Es un efecto secundario agradable: puedes emitir un certificado incluso para un servicio que no está conectado a internet.

Instala Certbot y las herramientas de DNS:

apt update
apt install -y certbot bind9-dnsutils

El paquete bind9-dnsutils aporta dig. En los cuatro sistemas, el nombre antiguo dnsutils ya no es más que un paquete de transición que apunta a bind9-dnsutils. Comprueba qué versión de Certbot te ha tocado:

certbot --version

Las diferencias son considerables y deciden qué vía tienes realmente disponible:

SistemaCertbotPlugins en los repositorios de la distribución
Debian 134.0.0cloudflare, desec, google, infomaniak, rfc2136, route53
Debian 122.1.0cloudflare, digitalocean, dnsimple, gandi, gehirn, google, linode, ovh, rfc2136, route53, sakuracloud
Ubuntu 24.042.9.0cloudflare, digitalocean, dnsimple, gandi, gehirn, google, infomaniak, linode, ovh, rfc2136, route53, sakuracloud
Ubuntu 22.041.21.0cloudflare, digitalocean, dnsimple, gandi, gehirn, google, linode, ovh, rfc2136, route53, sakuracloud

Y aquí viene la sorpresa: Debian 13 ha sacado del archivo la mayoría de los plugins de DNS. Si en Debian 12 trabajabas con python3-certbot-dns-ovh o python3-certbot-dns-linode y actualizas a Debian 13, allí sencillamente ya no encontrarás el paquete. Un apt upgrade que cruza la frontera entre distribuciones lo elimina, y la renovación se interrumpe sin que nadie se dé cuenta. Comprueba esto antes de cambiar de distribución.

Qué plugins se cargan realmente en tu sistema lo ves con:

certbot plugins

La vía manual con un registro TXT

La vía manual no necesita acceso a ninguna API y funciona con cualquier proveedor de DNS. Sirve para hacer pruebas y para zonas que de todas formas tocas pocas veces. Tiene un inconveniente serio, al que llegamos enseguida.

Baja antes el TTL del futuro registro TXT en tu zona, entre 60 y 300 segundos. No cuesta nada y te ahorra espera después. Entonces:

certbot certonly --manual --preferred-challenges dns \
  --cert-name midominio.es \
  -d "*.MiDominio.es" -d MiDominio.es

Las comillas alrededor de "*.MiDominio.es" son obligatorias. Sin ellas, el shell sustituye el asterisco por los nombres de archivo del directorio actual y Certbot solicita certificados para tus archivos. --cert-name también es muy recomendable: de lo contrario, Certbot deriva el nombre del directorio del certificado a partir del primer nombre, y nadie quiere tener que buscar un directorio con un asterisco en el nombre.

Certbot se detiene y muestra algo así:

Please deploy a DNS TXT record under the name:
_acme-challenge.MiDominio.es.

with the following value:

gfj9Xq...Rg85nM

Aquí llega el punto en el que fracasan la mayoría de los intentos. Has solicitado dos nombres, el asterisco y el dominio raíz. Son dos validaciones separadas y ambas caen sobre el mismo nombre de registro _acme-challenge.MiDominio.es, con dos valores distintos. Certbot lo dice claramente:

This must be set up in addition to the previous challenges; do not remove, replace, or undo the previous challenge tasks yet. Note that you might be asked to create multiple distinct TXT records with the same name. This is permitted by DNS standards.

Sin embargo, muchos paneles de DNS ofrecen un único campo de entrada para el mismo nombre y sustituyen el primer valor por el segundo. Al final solo queda un valor en la zona, una de las dos validaciones falla y el mensaje de error menciona igualmente un solo nombre. Si tu panel no admite dos registros TXT con el mismo nombre, eso descarta la vía manual. Usa entonces la delegación por CNAME, más abajo.

Comprueba primero, pulsa Enter después

Certbot espera tu confirmación. No pulses Enter de inmediato. Abre una segunda sesión SSH y consulta primero el servidor de nombres autoritativo, no el resolver local:

dig +short NS MiDominio.es
dig +short TXT _acme-challenge.MiDominio.es @ns1.proveedor.example
dig +short TXT _acme-challenge.MiDominio.es @1.1.1.1
dig +short TXT _acme-challenge.MiDominio.es @8.8.8.8

Pulsa Enter solo cuando ambos valores aparezcan en una misma consulta, y además en varios resolvers independientes entre sí. Una salida vacía significa que el registro todavía no está ahí. Así se ve en un dominio sin registro, la salida queda vacía:

dig +short TXT _acme-challenge.example.com @1.1.1.1

¿Por qué ese rodeo por el servidor autoritativo? Si consultas _acme-challenge antes de haber creado el registro, tu resolver memoriza esa inexistencia durante todo el TTL negativo que indica el registro SOA, a menudo una hora entera. Entonces no ves nada durante mucho rato aunque el registro lleve tiempo publicado, y buscas el fallo en el sitio equivocado. El servidor autoritativo no tiene esa caché.

La pega de la vía manual

Un certificado emitido con --manual y sin script no se renueva nunca solo. En la siguiente ejecución automática, el log dice:

An authentication script must be provided with --manual-auth-hook when using
the manual plugin non-interactively.

Certbot se salta ese certificado y sigue con los demás, y el código de salida suele parecer inofensivo. Te enteras cuando el navegador protesta. Cuenta, por tanto, con repetir el mismo procedimiento a mano cada 60 a 90 días, o cambia a una de las vías automáticas.

La vía automática con un plugin del proveedor

Si tu proveedor de DNS tiene una API y existe un plugin de Certbot para ella, Certbot crea el registro TXT por su cuenta, espera, deja que se valide y lo vuelve a retirar. Esta es la vía que quieres para sistemas en producción. Con Cloudflare como ejemplo:

apt install -y python3-certbot-dns-cloudflare

Guarda las credenciales fuera del directorio web y protégelas de inmediato:

mkdir -p /root/.secrets/certbot
chmod 700 /root/.secrets/certbot

En /root/.secrets/certbot/cloudflare.ini va exactamente una línea:

dns_cloudflare_api_token = TuTokenAqui

Después, sin falta:

chmod 600 /root/.secrets/certbot/cloudflare.ini

De lo contrario, Certbot avisa en cada ejecución de que los permisos son demasiado amplios. Usa un token restringido con permiso de escritura sobre los registros DNS, no la clave global de la cuenta. La clave global todavía funciona, pero puede hacer cualquier cosa en tu cuenta y luego queda en texto plano en el servidor. Un token se puede limitar a una única zona y revocar por separado si algo sale mal.

La emisión:

certbot certonly --dns-cloudflare \
  --dns-cloudflare-credentials /root/.secrets/certbot/cloudflare.ini \
  --dns-cloudflare-propagation-seconds 60 \
  --cert-name midominio.es \
  -d "*.MiDominio.es" -d MiDominio.es

El valor de --propagation-seconds es el tiempo de espera entre la creación del registro y la consulta de validación. El valor por defecto son 10 segundos y se queda corto para muchas zonas. 60 segundos son un buen punto de partida, y 120 con proveedores lentos. Esta única cifra explica buena parte de las renovaciones que fallan de forma esporádica, esas que nadie más consigue reproducir.

Para otros proveedores el paquete se llama python3-certbot-dns-<proveedor>, y los parámetros siguen el mismo patrón. Mira la tabla de arriba para saber si tu proveedor está siquiera disponible en tu distribución.

Cuando falta el paquete adecuado

El reflejo inmediato es pip install certbot-dns-loquesea. En Debian 12, Debian 13 y Ubuntu 24.04 eso termina así:

error: externally-managed-environment

× This environment is externally managed

Es intencionado, no es un defecto. En Ubuntu 22.04 el mismo comando todavía pasa, pero entonces mezcla paquetes de pip con los paquetes del sistema, y en el siguiente apt upgrade las versiones de Certbot y del plugin ya no encajan. No lo fuerces con --break-system-packages.

La salida limpia es la versión snap de Certbot, que trae los plugins consigo y se mantiene actualizada sola. Quita antes el paquete de la distribución para que no haya dos Certbot administrando el mismo directorio:

apt remove -y certbot
snap install --classic certbot
ln -s /snap/bin/certbot /usr/bin/certbot
snap set certbot trust-plugin-with-root=ok
snap install certbot-dns-cloudflare

Importante: los datos existentes en /etc/letsencrypt/ se conservan, el snap los adopta. Pero después el timer de systemd se llama snap.certbot.renew.timer y ya no certbot.timer. Quien pasa eso por alto acaba con dos timers o con ninguno.

Sin plugin del proveedor: rfc2136 y delegación CNAME

Dos métodos funcionan sea quien sea tu proveedor de DNS.

rfc2136 es la vía estándar para la actualización dinámica de DNS con clave TSIG. Funciona con BIND, Knot y PowerDNS, y está disponible como paquete en los cuatro sistemas:

apt install -y python3-certbot-dns-rfc2136

Si gestionas tú mismo tu DNS, esta es la solución más robusta, porque no necesita ningún servicio ajeno ni ninguna API HTTP.

La delegación CNAME es la respuesta más elegante a dos problemas a la vez. En tu zona principal creas una sola vez un registro que ya no volverá a cambiar:

_acme-challenge.MiDominio.es.  CNAME  MiDominio.es.acme.otra-zona.es.

Let's Encrypt sigue las cadenas de CNAME cuando busca el registro TXT. El registro TXT se crea entonces en la zona de destino, y solo ahí necesita el servidor permisos de escritura. Eso resuelve varias cosas de golpe:

  • Las credenciales que hay en el servidor web no pueden modificar tu zona principal. Un servidor web comprometido no puede desviar registros MX.
  • La zona de destino puede llevar varios valores TXT a la vez, aunque el panel de tu proveedor principal no sepa hacerlo.
  • La zona de destino puede tener un TTL muy bajo sin que la zona principal lo sufra.

El propio CNAME no se vuelve a tocar nunca, así que puede tener un TTL alto. Se comprueba con:

dig +short CNAME _acme-challenge.MiDominio.es @1.1.1.1

Automatizar la renovación

El paquete de Certbot ya trae un timer que se ejecuta dos veces al día. Un certificado solo se renueva cuando hace falta:

systemctl list-timers certbot.timer

Si no está en marcha:

systemctl enable --now certbot.timer

En los cuatro sistemas el paquete trae dos disparadores: /lib/systemd/system/certbot.timer y, además, /etc/cron.d/certbot. El archivo de cron comprueba por sí mismo al principio si el timer está activo y en ese caso no hace nada, así que no se renueva dos veces. Por eso no necesitas un cron de renovación propio, sería el tercer disparador para la misma tarea.

Aquí hay una diferencia entre distribuciones que conviene conocer. Certbot hasta la versión 3 renueva cuando quedan menos de 30 días de validez. Certbot 4.0, es decir, la versión que trae Debian 13, renueva en cambio cuando queda un tercio de la vida útil. Con los 90 días habituales hoy, ambos llegan al mismo momento. En cuanto Let's Encrypt emita certificados de duración más corta, las dos versiones se comportarán de forma distinta, y solo la nueva se adapta automáticamente.

Prueba el proceso en seco. Así no se emite nada ni se consume ningún límite:

certbot renew --dry-run

Certbot no escribe un certificado wildcard en la configuración del servidor web, eso tienes que hacerlo tú una vez. Para que el servidor web cargue de verdad el certificado nuevo después de cada renovación, crea un deploy hook. Escríbelo como archivo, no como parámetro:

mkdir -p /etc/letsencrypt/renewal-hooks/deploy

El contenido de /etc/letsencrypt/renewal-hooks/deploy/reload-webserver.sh:

#!/bin/sh
systemctl reload nginx

Después, hazlo ejecutable:

chmod +x /etc/letsencrypt/renewal-hooks/deploy/reload-webserver.sh

Los scripts de este directorio se ejecutan para cada certificado renovado. El parámetro --deploy-hook, en cambio, solo se escribe en el archivo de renovación de aquellos certificados que se trataron justo en ese momento. Los certificados añadidos más tarde no lo tienen, y eso no se nota hasta meses después.

Una advertencia sobre las credenciales de la API: si el proveedor revoca el token o este caduca, la renovación falla sin que nada parezca roto. El certificado sigue siendo válido, al fin y al cabo. El servicio no se cae hasta 30 días más tarde. Comprueba por eso de vez en cuando que los certificados realmente se están renovando, y no te fíes de los avisos de caducidad por correo.

Cómo saber que de verdad ha funcionado

Que el comando terminara sin errores no demuestra nada. Estas tres comprobaciones sí. Primero, el resumen:

certbot certificates

Bajo Domains tienen que figurar ambos nombres, *.MiDominio.es y MiDominio.es. Si falta el asterisco, has obtenido un certificado normal y simplemente no te has dado cuenta.

Segundo, un vistazo al propio archivo:

openssl x509 -noout -text -in /etc/letsencrypt/live/midominio.es/fullchain.pem | grep -A1 "Subject Alternative Name"

Ahí tiene que aparecer DNS:*.MiDominio.es. El campo determinante es Subject Alternative Name, no el Common Name, que los navegadores modernos ya ni siquiera evalúan.

Tercero, y esta es la prueba de verdad, consulta un nombre que te acabes de inventar:

echo | openssl s_client -servername test-1234.MiDominio.es -connect MiDominio.es:443 2>/dev/null | openssl x509 -noout -subject -dates

Si eso devuelve un certificado válido y ninguna advertencia, el wildcard funciona de verdad. Solo entonces has terminado.

Errores frecuentes, palabra por palabra

  • "DNS problem: NXDOMAIN looking up TXT for _acme-challenge.MiDominio.es": el registro no existe, todavía no se ha propagado, o lo has creado en el proveedor equivocado. El caso más frecuente: el dominio está registrado en el proveedor A, pero los servidores de nombres apuntan al proveedor B y el registro está en A. Lo único que cuenta es lo que devuelve dig +short NS MiDominio.es.
  • "Incorrect TXT record ... found at _acme-challenge.MiDominio.es": hay un valor, pero es el equivocado. Típico después de una interrupción, cuando el valor antiguo sigue en la zona, o cuando el panel ha escrito el segundo valor encima del primero. Borra los registros _acme-challenge antiguos y empieza de nuevo.
  • "DNS problem: SERVFAIL looking up TXT ... the domain's nameservers may be malfunctioning": casi siempre una firma DNSSEC rota, por ejemplo tras un cambio de proveedor en el que el registro DS antiguo se quedó publicado en la registry. Repáralo antes, o cualquier emisión fallará.
  • "CAA record for MiDominio.es prevents issuance": la piedra en el camino que más a menudo se pasa por alto. Para los wildcards, la autoridad de certificación evalúa primero issuewild. Quien ha puesto issue "letsencrypt.org", pero tiene al lado un issuewild ";", obtiene certificados normales y ningún wildcard. Compruébalo con dig +short CAA MiDominio.es.
  • "too many certificates (5) already issued for this exact set of identifiers": para la misma combinación de nombres se permiten cinco certificados en siete días. Prueba por eso con --dry-run o contra el entorno de pruebas con --test-cert. El bloqueo caduca por sí solo y no hay forma de levantarlo.
  • El panel muestra el registro TXT, pero dig no: algunos proveedores exigen que los cambios en la zona se publiquen de forma explícita. Un registro guardado no es automáticamente un registro activo.
  • El nombre del registro queda duplicado: algunos paneles añaden el dominio automáticamente. Escribe ahí solo _acme-challenge, o acabarás con _acme-challenge.MiDominio.es.MiDominio.es. Un dig sobre el nombre completo lo delata al instante.

Perspectiva: DNS-PERSIST-01

Let's Encrypt trabaja en un nuevo método de validación llamado DNS-PERSIST-01. En lugar de publicar un token nuevo en cada renovación, depositas una sola vez un registro permanente que autoriza a una cuenta ACME concreta a emitir certificados. A partir de ahí, el servidor ya no necesita ningún permiso de escritura en el DNS para renovar. Para los wildcards eso supondría una ganancia clara en seguridad.

Según la hoja de ruta de Let's Encrypt, el entorno de pruebas estaba previsto para finales del primer trimestre de 2026 y el funcionamiento en producción para el segundo trimestre. Por ahora no se ha anunciado si Certbot admitirá el método. Así que todavía no cuentes con él, pero tenlo en el radar si justo ahora estás montando de cero una gestión de certificados.

Resumen

Un certificado wildcard solo es posible mediante la validación por DNS, porque un asterisco no se puede demostrar a través de un único servidor web. La vía manual con un registro TXT funciona en todas partes, pero no se renueva nunca sola. La vía del plugin del proveedor es la única que puedes dejar funcionando sin supervisión, y en la práctica suele fallar por un tiempo de espera demasiado corto o por credenciales que se han invalidado en silencio. Si solo te quedas con una cosa: comprueba el registro TXT en el servidor de nombres autoritativo antes de dejar que Certbot siga adelante, y cuenta con dos registros bajo el mismo nombre en cuanto pidas el asterisco más el dominio raíz.

Preguntas frecuentes

¿Por qué no puedo emitir un certificado wildcard con Apache o nginx?
Porque la validación a través del servidor web consulta un archivo bajo un nombre de host concreto. Un wildcard, en cambio, cubre una cantidad indeterminada de nombres, incluidos los que todavía no existen. Por eso Let's Encrypt solo acepta la validación DNS para los wildcards. Si no, Certbot informa: Client with the currently selected authenticator does not support any combination of challenges that will satisfy the CA.
¿El certificado *.MiDominio.es cubre también MiDominio.es?
No. Un wildcard vale solo para los nombres situados exactamente un nivel por debajo. El dominio raíz tienes que solicitarlo aparte, con un parámetro -d adicional. Tampoco vale dos niveles más abajo, es decir, no cubre a.b.MiDominio.es.
¿Por qué necesito dos registros TXT con el mismo nombre?
Porque el asterisco y el dominio raíz son dos validaciones separadas que caen ambas en _acme-challenge.MiDominio.es, con valores distintos. Los dos tienen que estar en la zona al mismo tiempo. El estándar DNS lo permite, pero muchos paneles de proveedores sustituyen el primer valor por el segundo.
¿Cómo compruebo si el registro TXT ya se ha propagado?
Con dig contra el servidor de nombres autoritativo y, además, contra al menos dos resolvers públicos, por ejemplo dig +short TXT _acme-challenge.MiDominio.es @1.1.1.1. No consultes el nombre antes de haber creado el registro, o tu resolver memorizará esa inexistencia durante todo el TTL negativo.
¿Se renueva automáticamente un certificado wildcard emitido a mano?
No. Sin script, Certbot informa en la ejecución automática: An authentication script must be provided with --manual-auth-hook when using the manual plugin non-interactively, y se salta el certificado. Para un funcionamiento sin supervisión necesitas un plugin de DNS de tu proveedor o rfc2136.
¿Qué plugins de DNS para Certbot hay en Debian 13?
Debian 13 ya solo trae cloudflare, desec, google, infomaniak, rfc2136 y route53. Paquetes como python3-certbot-dns-ovh, dns-linode o dns-digitalocean, que sí siguen presentes en Debian 12 y en Ubuntu, allí ya no están incluidos. Comprueba esto antes de cambiar de distribución, o la renovación se interrumpirá sin que lo notes.
¿Qué aporta una delegación CNAME de _acme-challenge?
Traslada el registro TXT a una zona separada. El servidor ya no necesita permisos de escritura sobre tu zona principal, así que un servidor web comprometido no puede modificar registros MX ni A. Además resuelve el problema de los paneles que no admiten dos valores TXT bajo el mismo nombre.

Lets-Encrypt Certbot Wildcard Certificado SSL DNS DNS-01 Debian Ubuntu