Hello guys, let me share you how I managed to solve this problem using letsencrypt requesting wildcard on external DNS. It works perfectly. I hope this post will help somebody here. You don't need to edit any other things, (no need to manually add record in external DNS like _acme-challenge-test or _acme-challenge, no need to increase the DIG_SECONDS like what I said before even you don't have to touch letsencrypt.sh) just one piece of script and it will manage everything. I'm using Linode to manage my DNS externally but I think this guide is likely the same for other DNS providers like GoDaddy, DigitalOcean.. etc. Even you can use various DNS providers to renew this wildcard certs.. In this case you need to check in your script which registrar is the domain that you are trying to request wildcard certs is in, if it belongs to GoDaddy then, call GoDaddy API else call DigitalOcean API... etc. But in my case I only use Linode so only 1 API is needed:
1) First step, I need to be able to use API in my server in order to be able to do add, delete domain. Here is the link for Linode how to install linode-cli in my server:
https://www.linode.com/docs/platform/api/linode-cli/ . I followed the instruction there how to install the API until I'm able to fire up linode-cli -h in my server terminal and I do add, delete operation for testing in terminal. For GoDaddy I think it would be here.
https://developer.godaddy.com/. The important requirement is that, your external DNS must have API to do basic domain operation like add, delete.
2) Then as pointed by
@smtalk we can use taskq_dns_post.sh in /usr/local/directadmin/scripts/custom/taskq_dns_post.sh
3) For this taskq_dns_post.sh, I have the following content (for add, delete and update operation (just in case), you can see the API documentation):
Code:
#!/bin/sh
# taskq_dns_post.sh
# Author - Arafat Ali : github.com/arafatx | gitlab.com/arafatx
# [email protected]
#DA wildcard request for linode external DNS
LPYENV="/home/admin/maxipy/bin/./linode-cli"
MYEMAIL="[email protected]"
MYHOSTNAME=`/bin/hostname`
WARNING_STATUS="OK"
MAIL_BIN="/usr/local/bin/mail"
REPORT_FILE="/tmp/taskq-dns-post-report.log"
cat /dev/null > $REPORT_FILE
#DEFINED VARIABLES PASSED FOR taskq_dns_post.sh:
ACTION=$action
DOMAIN=$domain
DO_ACTION=$do
TYPE=$type
NAME=$name
VALUE=`echo "$value" | tr -d '"'`
NAMED_RELOAD=$named_reload
TTL=$ttl
DNS_USER_TTL_SEC="5" # Decrease this value the lowest possible for best propagation. You can see what is the lowest TTL value can go in your DNS from your external DNS record manager.
# get Domain ID
getLinodeDomainID()
{
$LPYENV domains list --domain $DOMAIN --pretty --json |
fgrep '"id":' |
sed -r 's/[^0-9]//g'
}
# get existing DNS record name
getDNSRecordNameACME()
{
LinodeDomainID=$( getLinodeDomainID $DOMAIN)
if [ -n "$LinodeDomainID" ]; then
if [ "$NAME" == "_acme-challenge" ]; then
$LPYENV domains records-list --page 2 $LinodeDomainID --pretty --json | grep "\"name\": \"$NAME\"" | grep -o '_acme[^"]challenge'
elif [ "$NAME" == "_acme-challenge-test" ]; then
$LPYENV domains records-list --page 2 $LinodeDomainID --pretty --json | grep "\"name\": \"$NAME\"" | grep -o '_acme[^"]challenge-test'
fi
fi
}
#get existing DNS record ID
getDNSRecordID()
{
LinodeDomainID=$(getLinodeDomainID $DOMAIN)
if [ -n "$LinodeDomainID" ]; then
$LPYENV domains records-list --page 2 $LinodeDomainID --pretty --json | grep "\"name\": \"$NAME\"" -B 1 | head -n 1 | sed -r 's/[^0-9]//g'
fi
}
# Sync the records. whenever DA add record in local DNS, it will pass DO_ACTION value, so we can use it to sync to external DNS:
if [ "$DO_ACTION" == "add" ]; then
#addDNSRecord
echo "[taskq_dns_post | info]: Operation: $DO_ACTION, ACTION: $ACTION" | tee -a $REPORT_FILE
LinodeDomainID=$(getLinodeDomainID $DOMAIN)
DNSRecordName=$(getDNSRecordNameACME $DOMAIN)
if [ -z "$DNSRecordName" ]; then # If record empty then add new
echo "[taskq_dns_post | info]: OK, existing record name not found. adding new record ... " | tee -a $REPORT_FILE
$LPYENV domains records-create $LinodeDomainID --pretty --json \
--name $NAME \
--target $VALUE \
--type $TYPE \
--ttl_sec $DNS_USER_TTL_SEC
echo "[taskq_dns_post | info]: OK, DNS record entry $NAME for $DOMAIN has been added with TTL_SEC = \"$DNS_USER_TTL_SEC\"." | tee -a $REPORT_FILE
#sleep 60m
else
WARNING_STATUS="WARNING"
echo "[taskq_dns_post | info]: Warning, unable to add new record for \"$NAME\" because existing record name \"$DNSRecordName\" is found." | tee -a $REPORT_FILE
fi
elif [ "$DO_ACTION" == "delete" ]; then
#deleteDNSRecord
echo "[taskq_dns_post | info]: Operation: $DO_ACTION, ACTION: $ACTION" | tee -a $REPORT_FILE
LinodeDomainID=$(getLinodeDomainID $DOMAIN)
DNSRecordID=$(getDNSRecordID $DOMAIN)
if [ -n "$DNSRecordID" ]; then # If ID exist (not empty) then can do delete operation
echo "[taskq_dns_post | info]: OK, DNSRecordID found "
$LPYENV domains records-delete $LinodeDomainID $DNSRecordID
echo "[taskq_dns_post | info]: OK, DNS record entry $NAME for $DOMAIN has been deleted." | tee -a $REPORT_FILE
else
echo "[taskq_dns_post | info]: Warning, unable to delete existing record for $DOMAIN because DNSRecordID returns empty value." | tee -a $REPORT_FILE
fi
elif [ "$DO_ACTION" == "update" ]; then
#updateDNSRecord
echo "[taskq_dns_post | info]: Operation: $DO_ACTION, ACTION: $ACTION" | tee -a $REPORT_FILE
LinodeDomainID=$(getLinodeDomainID $DOMAIN)
DNSRecordID=$(getDNSRecordID $DOMAIN)
if [ -n "$DNSRecordID" ]; then # If record ID exist, then can do update operation
echo "[taskq_dns_post | info]: OK, DNSRecordID found "
$LPYENV domains records-update $LinodeDomainID $DNSRecordID --pretty --json \
--name $NAME \
--target $VALUE \
--ttl_sec $DNS_USER_TTL_SEC
echo "[taskq_dns_post | info]: OK, DNS record for $DOMAIN with entry name \"$NAME\" has been updated to \"$VALUE\" with TTL_SEC = \"$DNS_USER_TTL_SEC\"" | tee -a $REPORT_FILE
else
echo "[taskq_dns_post | info]: Warning, unable to update existing record for $DOMAIN because DNSRecordID returns empty value." | tee -a $REPORT_FILE
fi
else
echo "[taskq_dns_post | info]: Warning, unknown action: $DO_ACTION" | tee -a $REPORT_FILE
fi
echo "=====================================================================" | tee -a $REPORT_FILE
$MAIL_BIN -s "[letsencrypt | $DOMAIN | $WARNING_STATUS]: Post Script has executed $DO_ACTION operation @ $MYHOSTNAME" $MYEMAIL < $REPORT_FILE
4) Make sure to chmod +x taskq_dns_post.sh
5) Then you can test requesting wildcard certificate
How does it work? With this script, everytime when letsencrypt trying to add or delete a domain, it will also do the same operation on the external domain in linode external DNS. So, both records are matching. The time to request this wildcard certificate took less than 1 minute on my linode external DNS. You also will get email about the delete,add operation that is happening on the external DNS. You can change the email in this script. You see this script basically the same structure and you just need to know how to call it using API on other registrars. Also the renewing process is working perfectly like what it is used to be. In case if the record is not propagate in time for some unknown reasons, you can uncomment this value #sleep 60m and let the script to wait for few minutes before verification: example 15 minutes sleep:
sleep 15m. But for linode DNS, I don't have this propagation issue so I comment this out (Maybe I put the lowest TTL here: (DNS_USER_TTL_SEC="5"). When you use this sleep function, keep in mind that letsencrypt actually execute taskq_dns_post.sh more than 1 time until the validation is success where the basic operation is to delete and to add record instead of using update method. (
https://forum.directadmin.com/threads/question-on-taskq_dns_post-sh.61615/). So if you put sleep 15m, and if the taskq_dns_post.sh is called 3 times for the add operation, until it success, then you are going to wait for 15minutes X 3 ~ 45 minutes for the script to finish executed in one request!
Here is the screenshot for requesting this wildcard certs when my domain is on external DNS: