Wildcard certs with (almost) any DNS host
A feature request a few weeks back asked that Neth integrate issuance of wildcard TLS certificates from Let’s Encrypt. This requires DNS validation, which (as a practical matter) requires that your DNS provider have an API to allow for automated updates. Most DNS hosts don’t have APIs. Of the ones that do, most aren’t supported by certbot or other ACME clients. Of the ones that are, the credentials required are all different. And in almost all cases, the credentials used are far more powerful than needed–if an attacker were to gain control of them, he could do anything to the DNS records for your entire domain. As a result, it’s near-impossible to package something that will work for everyone.
But “near-impossible” isn’t the same as “impossible”. The way to do this involves setting up your own DNS server, whose only purpose will be to respond to the validation queries that the Let’s Encrypt servers issue. This server will have an API, and a hook script for certbot will handle the necessary DNS updates. You’ll need to set up a number of DNS records with your DNS host at the outset, but they won’t need to be updated afterward.
This guide will describe how to install and configure acme-dns on your server, and how to make the necessary DNS records on your hosting provider. It will also cover installation of the hook script and obtaining the certificate. In the instructions below, replace
example.com with your actual domain name.
It’s my hope that smarter people than I will package this up so that it can be deployed as a Neth module.
As noted above, you’ll need to manually set up some records on your DNS host. Some will be set up now; I’ll describe others in the section on issuing the certificate. You’re going to be hosting a DNS server that covers a subdomain of your domain. Specifically, if your domain is
example.com, your acme-dns installation will be providing service for
acme.example.com. Log into your DNS host and add these records:
ns1.acme.example.com A $EXTERNAL_IP
ns2.acme.example.com A $EXTERNAL_IP
acme.example.com NS ns1.acme.example.com
acme.example.com NS ns2.acme.example.com
acme-dns is not available as an RPM in any of the standard repositories (to my knowledge), but its author does have a precompiled binary available for download. Download this package onto your server, extract it using
tar zxf, and move the acme-dns binary to
Create a configuration file at
/etc/acme-dns/config.cfg using your favorite text editor. Its contents should look like this:
# dns interface
listen = "$EXTERNAL_IP:1053"
# protocol, "udp", "udp4", "udp6" or "tcp", "tcp4", "tcp6"
protocol = "udp"
# domain name to serve the requests off of
domain = "acme.example.com"
# zone name server
nsname = "ns1.acme.example.com"
# admin email address, where @ is substituted with .
nsadmin = "admin.example.com"
# predefined records served in addition to the TXT
records = [
# default A
"acme.example.com. A $EXTERNAL_IP",
"ns1.acme.example.com. A $EXTERNAL_IP",
"ns2.acme.example.com. A $EXTERNAL_IP",
"acme.example.com. NS ns1.acme.example.com.",
"acme.example.com. NS ns2.acme.example.com.",
# debug messages from CORS etc
debug = false
# Database engine to use, sqlite3 or postgres
engine = "sqlite3"
# Connection string, filename for sqlite3 and postgres://$username:$password@$host/$db_name for postgres
# Please note that the default Docker image uses path /var/lib/acme-dns/acme-dns.db for sqlite3
connection = "/etc/acme-dns/acme-dns.db"
# connection = "postgres://user:password@localhost/acmedns_db"
# domain name to listen requests for, mandatory if using tls = "letsencrypt"
api_domain = ""
# listen ip eg. 127.0.0.1
ip = "0.0.0.0"
# disable registration endpoint
disable_registration = false
# autocert HTTP port, eg. 80 for answering Let's Encrypt HTTP-01 challenges. Mandatory if using tls = "letsencrypt".
# autocert_port = "80"
# listen port, eg. 443 for default HTTPS
port = "8675"
# possible values: "letsencrypt", "cert", "none"
tls = "none"
# only used if tls = "cert"
#tls_cert_privkey = "/etc/tls/example.org/privkey.pem"
#tls_cert_fullchain = "/etc/tls/example.org/fullchain.pem"
# CORS AllowOrigins, wildcards can be used
corsorigins = [
# use HTTP header to get the client ip
use_header = false
# header name to pull the ip address / list of ip addresses from
header_name = "X-Forwarded-For"
# logging level: "error", "warning", "info" or "debug"
loglevel = "debug"
# possible values: stdout, TODO file & integrations
logtype = "stdout"
# file path for logfile TODO
# logfile = "./acme-dns.log"
# format, either "json" or "text"
logformat = "text"
Next, you’ll need to create a service file for systemd. Using your favorite text editor, create
/etc/systemd/system/acme-dns.service with the contents below:
You can then enable and start the service by running
systemctl enable --now acme-dns.
Next, we’ll need to open port 53 on the Internet (red) interface and redirect it to port 1053 for acme-dns to listen to it. Create
/etc/e-smith/templates-custom/etc/shorewall/rules/95acme with the following contents:
REDIRECT net 1053 tcp 53
REDIRECT net 1053 udp 53
config set fw_acme-dns service status enabled UDPPort 53 TCPPort 53 access red
Installation of acme-dns is now complete.
The hook script
Certbot doesn’t know, on its own, how to set DNS entries on acme-dns. Fortunately, the author of acme-dns has provided a script to handle this. Download the script by doing
curl -o /etc/letsencrypt/acme-dns-auth.py https://raw.githubusercontent.com/joohoi/acme-dns-certbot-joohoi/master/acme-dns-auth.py followed by
chmod 0700 /etc/letsencrypt/acme-dns-auth.py.
You’ll need to edit the script. Near the top, change ACMEDNS_URL to
"http://localhost:8675". No other changes need to be made.
Issuing the certificate
All the prep work is now done, and it’s time to issue the certificate. Run
certbot certonly --manual --manual-auth-hook /etc/letsencrypt/acme-dns-auth.py \
--preferred-challenges dns --debug-challenges --post-hook "signal-event certificate-update" \
-d example.com -d \*.example.com
If you want more domains, add them with additional -d flags at the end. Certbot will ask you a few questions (email address, agree to TOS, share email with EFF, log IP address), and then show a message like this:
Output from acme-dns-auth.py:
Please add the following CNAME record to your main DNS zone:
_acme-challenge.example.com CNAME 32f5274d-51e3-466d-bf38-eb9980e7bcf3.acme.example.com.
Waiting for verification...
Challenges loaded. Press continue to submit to CA. Pass "-v" for more info about
Press Enter to Continue
You’ll need to once again log into your DNS host and add the record specified. Wait a few minutes, then press Enter.
At this point, your certificate is issued. A daily cron job of
certbot renew will attempt to renew it whenever it has less than 30 days’ validity remaining. No further updates need to be made to your DNS host; all those records are static. The validation records (which do change for every renewal) are served by acme-dns running on your server.
pki properties to match cert files that will be generated
- Open and close firewall ports when certbot runs
signal-event certificate-update on issuance/renewal
- Use HTTPS for acme-dns API
- Open firewall port for API