1. Home
  2. Knowledge Base
  3. Let’s Encrypt wildcard con Plesk API (DNS-01)

Let’s Encrypt wildcard con Plesk API (DNS-01)

Cuando necesitas emitir un certificado Let’s Encrypt para un dominio (y especialmente un wildcard tipo *.tudominio.com), el método recomendado es el desafío DNS-01. Este desafío consiste en publicar temporalmente un registro TXT en el DNS con un token que valida que eres el propietario del dominio.

En este post vamos a automatizar el proceso usando Certbot en modo manual, pero con hooks que se encargan de:

  • Crear el TXT _acme-challenge vía Plesk API (agent.php) antes de validar (--manual-auth-hook)
  • Eliminarlo al finalizar (--manual-cleanup-hook)
  • Esperar propagación DNS antes de que Certbot continúe, evitando fallos por “aún no se ve el TXT”.

La ventaja de este método es que no dependes de plugins específicos, puedes usarlo con DNS gestionado desde Plesk y sirve para wildcard.

Requisitos previos

  • Tener certbot instalado en el servidor donde emitirás el certificado.
  • Tener acceso admin al Plesk que gestiona la zona DNS del dominio.
  • Tener instaladas herramientas básicas:
    • curl
    • xmllint (normalmente en libxml2-utils)
    • awk, sed, tr
    • nslookup (paquete dnsutils en Debian/Ubuntu)

Preparación

  1. Crea el dominio (Si no existe):
    • sudo mkdir -p /etc/letsencrypt
  2. Crea el script auth hook:
    • Guárdalo como:
      • /etc/letsencrypt/plesk_dns_hook.sh
    • y dale permisos de ejecución.
      • sudo nano /etc/letsencrypt/plesk_dns_hook.sh
      • sudo chmod +x /etc/letsencrypt/plesk_dns_hook.sh
    • Script: /etc/letsencrypt/plesk_dns_hook.sh
#!/bin/bash

# /etc/letsencrypt/plesk_dns_hook.sh
# Llamar como:

# certbot certonly --manual --preferred-challenges dns --manual-auth-hook /etc/letsencrypt/plesk_dns_hook.sh --manual-cleanup-hook /etc/letsencrypt/plesk_dns_cleanup.sh -d '*.occentus.net'

echo $CERTBOT_DOMAIN
echo $CERTBOT_VALIDATION

#sleep 20000

set -e

HTTP_AUTH_LOGIN="admin"
HTTP_AUTH_PASSWD="PASSWORD"
domain_plesk="plesk.occentus.net"
domain="occentus.net"
site_id="99"

# Certbot proporciona estas:
# CERTBOT_DOMAIN, CERTBOT_VALIDATION

# Obtener IDs de registros TXT previos para _acme-challenge
ids=$(curl -ks --connect-timeout 5 --max-time 10 -X POST https://$domain_plesk:8443/enterprise/control/agent.php \
  -H "HTTP_AUTH_LOGIN: $HTTP_AUTH_LOGIN" \
  -H "HTTP_AUTH_PASSWD: $HTTP_AUTH_PASSWD" \
  -d "<?xml version=\"1.0\"?>
<packet>
  <dns>
    <get_rec>
      <filter>
        <site-id>$site_id</site-id>
      </filter>
    </get_rec>
  </dns>
</packet>" \
| xmllint --format - \
| awk "/<id>/ { id=\$0 } /<host>_acme-challenge.${CERTBOT_DOMAIN}.<\/host>/ { print id }" \
| tr ' ' '\n' | sed '/^$/d' | sed 's/<id>//g' | sed 's#</id>##g')

# Borrar cada ID encontrado
for id in $ids; do
  curl -ks --connect-timeout 5 --max-time 10 -X POST https://$domain_plesk:8443/enterprise/control/agent.php \
    -H "HTTP_AUTH_LOGIN: $HTTP_AUTH_LOGIN" \
    -H "HTTP_AUTH_PASSWD: $HTTP_AUTH_PASSWD" \
    -d "<?xml version=\"1.0\"?>
<packet>
  <dns>
    <del_rec>
      <filter>
        <id>$id</id>
      </filter>
    </del_rec>
  </dns>
</packet>"
done

# Añadir el nuevo registro TXT
curl -ks --connect-timeout 5 --max-time 10 -X POST https://$domain_plesk:8443/enterprise/control/agent.php \
  -H "HTTP_AUTH_LOGIN: $HTTP_AUTH_LOGIN" \
  -H "HTTP_AUTH_PASSWD: $HTTP_AUTH_PASSWD" \
  -d "<?xml version=\"1.0\"?>
<packet version=\"1.6.3.0\">
  <dns>
    <add_rec>
      <site-id>$site_id</site-id>
      <type>TXT</type>
      <host>_acme-challenge</host>
      <value>$CERTBOT_VALIDATION</value>
    </add_rec>
  </dns>
</packet>"

# Esperar a que el registro TXT esté disponible en DNS público (máx. 60 minutos)
echo "Esperando propagación DNS para _acme-challenge.${CERTBOT_DOMAIN}..."
for i in {1..120}; do
  if nslookup -q=TXT "_acme-challenge.${CERTBOT_DOMAIN}" | grep -q "$CERTBOT_VALIDATION"; then
    echo "✔ Registro DNS propagado correctamente."
    break
  fi
  echo "⏳ Intento $i: Registro aún no visible. Esperando 60 segundos..."
  sleep 60
done

# Si después de 120 minutos no aparece, abortar
if ! nslookup -q=TXT "_acme-challenge.${CERTBOT_DOMAIN}" | grep -q "$CERTBOT_VALIDATION"; then
  echo "❌ ERROR: El registro TXT no se ha propagado tras 120 minutos."
  exit 1
fi
  1. Crea el script cleanup hook:
    • Guárdalo como:
      • /etc/letsencrypt/plesk_dns_cleanup.sh
    • y dale permisos de ejecución.
      • sudo nano /etc/letsencrypt/plesk_dns_cleanup.sh
      • sudo chmod +x /etc/letsencrypt/plesk_dns_cleanup.sh
    • Script: /etc/letsencrypt/plesk_dns_cleanup.sh
#!/bin/bash
# /etc/letsencrypt/plesk_dns_cleanup.sh
set -e

HTTP_AUTH_LOGIN="admin"
HTTP_AUTH_PASSWD="PASSWORD"
domain_plesk="plesk.occentus.net"
site_id="99"

# Obtener IDs del TXT a borrar
ids=$(curl -ks --connect-timeout 5 --max-time 10 -X POST https://$domain_plesk:8443/enterprise/control/agent.php \
  -H "HTTP_AUTH_LOGIN: $HTTP_AUTH_LOGIN" \
  -H "HTTP_AUTH_PASSWD: $HTTP_AUTH_PASSWD" \
  -d "<?xml version=\"1.0\"?>
<packet>
  <dns>
    <get_rec>
      <filter>
        <site-id>$site_id</site-id>
      </filter>
    </get_rec>
  </dns>
</packet>" \
| xmllint --format - \
| awk "/<id>/ { id=\$0 } /<host>_acme-challenge.${CERTBOT_DOMAIN}.<\/host>/ { print id }" \
| tr ' ' '\n' | sed '/^$/d' | sed 's/<id>//g' | sed 's#</id>##g')

#echo "$ids"
#sleep 30

for id in $ids; do
  curl -ks --connect-timeout 5 --max-time 10 -X POST https://$domain_plesk:8443/enterprise/control/agent.php \
    -H "HTTP_AUTH_LOGIN: $HTTP_AUTH_LOGIN" \
    -H "HTTP_AUTH_PASSWD: $HTTP_AUTH_PASSWD" \
    -d "<?xml version=\"1.0\"?>
<packet>
  <dns>
    <del_rec>
      <filter>
        <id>$id</id>
      </filter>
    </del_rec>
  </dns>
</packet>"
done

Configuración a revisar en los scripts

Antes de ejecutar, ajusta estas variables en ambos scripts:

  • HTTP_AUTH_LOGIN / HTTP_AUTH_PASSWD: credenciales para autenticar contra Plesk (según tengas configurada la API).
  • domain_plesk: hostname del Plesk, por ejemplo plesk.midominio.com
  • site_id: el ID del sitio/zona DNS en Plesk donde están los registros del dominio

Por limitación de Plesk, debe usarse la cuenta admin de Plesk para realizar estos cambios en los DNS.

Emitir el certificado con Certbot

Ejemplo para wildcard:

sudo certbot certonly \
  --manual \
  --preferred-challenges dns \
  --manual-auth-hook /etc/letsencrypt/plesk_dns_hook.sh \
  --manual-cleanup-hook /etc/letsencrypt/plesk_dns_cleanup.sh \
  -d '*.occentus.net'

Si además quieres el dominio raíz (recomendado en muchos casos), añade también:

-d 'occentus.net'

Quedaría:

sudo certbot certonly \
--manual \
--preferred-challenges dns \
--manual-auth-hook /etc/letsencrypt/plesk_dns_hook.sh \
--manual-cleanup-hook /etc/letsencrypt/plesk_dns_cleanup.sh \
-d 'occentus.net' \
-d '*.occentus.net'

Qué ocurre durante la ejecución

  1. Certbot inicia el desafío DNS-01 y exporta variables como:
    • CERTBOT_DOMAIN
    • CERTBOT_VALIDATION
  2. Se ejecuta el manual-auth-hook:
    • borra TXT antiguos _acme-challenge.<dominio>
    • crea el TXT nuevo con el token
    • espera hasta que nslookup lo vea públicamente
  3. Let’s Encrypt valida el dominio.
  4. Se ejecuta el manual-cleanup-hook y se eliminan los TXT creados.

Solución de problemas

  • No encuentra xmllint
    • Instala libxml2-utils.
  • No encuentra nslookup
    • Instala dnsutils.
  • La propagación tarda demasiado
    • Puede depender del TTL y de los servidores autoritativos. El script espera hasta 120 intentos de 60s (2 horas).
  • El TXT no coincide
    • Revisa si hay múltiples valores TXT y si el DNS está sirviendo cache antiguo.
  • Errores de autenticación en Plesk
    • Verifica credenciales y que la API por agent.php esté accesible desde tu servidor.

¿Necesitas ayuda?

No has podido encontrar la información que buscabas? Recuerda que estamos a tu disposición 24/7
Contactar con nuestro soporte