ACME clients and automation

How to get a wildcard certificate with acme.sh and a DNS API

By · Last verified

acme.sh gets a wildcard certificate by answering the DNS-01 challenge through your DNS provider's API: it creates the _acme-challenge TXT record, waits for validation and deletes it. One command issues example.com and *.example.com together, and the cron job renews and reloads the web server.

The commands use Cloudflare DNS; other providers differ only in variable names. They apply to acme.sh 3.1.4 or later on Linux, macOS or BSD.

Prerequisites

DNS-01 needs no open ports, only API access to the DNS zone.

  • A Unix-like server with curl or wget, openssl, cron and root (or a user that can write the certificate directory and reload the web server).
  • The domain's authoritative DNS hosted at a provider supported by acme.sh (list on the dnsapi wiki page).
  • An API credential limited to editing DNS records of that zone. For Cloudflare, create an API token with Zone → DNS → Edit for example.com, and note the Zone ID from the zone overview page.
  • Outbound HTTPS to the CA and the DNS provider's API.

Step 1: Install acme.sh

curl https://get.acme.sh | sh -s email=admin@example.com
exec $SHELL -l
acme.sh --version

The installer copies acme.sh to ~/.acme.sh/, adds an acme.sh alias and installs a cron job. The email is optional for Let's Encrypt.

Step 2: Switch the default CA to Let's Encrypt

acme.sh issues from ZeroSSL unless you change the default CA:

acme.sh --set-default-ca --server letsencrypt

You can also pass --server letsencrypt per command.

Step 3: Export the DNS API credentials

acme.sh reads DNS API credentials from environment variables named after the provider. For Cloudflare with a scoped token:

export CF_Token="your-cloudflare-api-token"
export CF_Zone_ID="your-zone-id"

Use CF_Account_ID instead of CF_Zone_ID if one token manages several zones in the same account. For other providers, look up the --dns dns_xxx name and variables on the acme.sh dnsapi wiki page.

Step 4: Issue the wildcard certificate

acme.sh --issue --dns dns_cf -d example.com -d '*.example.com'

acme.sh creates _acme-challenge TXT records through the API, waits for them to propagate, lets the CA validate and removes them again. Quote '*.example.com' so the shell does not expand it. To request Let's Encrypt's 45-day profile instead of the default, add --cert-profile tlsserver (see Let's Encrypt certificate profiles).

Step 5: Install the certificate and set the reload command

--install-cert copies the certificate to a stable path and saves a reload command that runs after every renewal. Do not point the web server at files inside ~/.acme.sh/, because their layout may change:

mkdir -p /etc/nginx/ssl
acme.sh --install-cert -d example.com --ecc \
  --key-file       /etc/nginx/ssl/example.com.key \
  --fullchain-file /etc/nginx/ssl/example.com.fullchain.pem \
  --reloadcmd      "systemctl reload nginx"

--ecc selects the ECDSA certificate, which is acme.sh's default key type. Then reference the files in Nginx:

ssl_certificate     /etc/nginx/ssl/example.com.fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/example.com.key;

Verify

Every covered host should present the same certificate, with example.com and *.example.com in its SAN list. Check any of them with the SSL checker, or from a terminal:

curl ssl.sh/example.com
curl ssl.sh/www.example.com

Locally, list what acme.sh manages and inspect the names in the certificate:

acme.sh --list
openssl x509 -in /etc/nginx/ssl/example.com.fullchain.pem -noout -ext subjectAltName -enddate

Automatic renewal

The installer's cron job runs acme.sh --cron; since 3.1.4 it runs every 6 hours and follows the CA's ARI renewal window by default (set NO_ARI=1 to disable). Without ARI, acme.sh renews on its own schedule, which you can override with --days; an explicit --days takes precedence over ARI since 3.1.5. After each renewal it re-runs the install step and your --reloadcmd.

crontab -l | grep acme.sh
acme.sh --cron --home ~/.acme.sh
acme.sh --upgrade --auto-upgrade

Test a full renewal against the staging CA (--staging) rather than forcing renewals in production, which count against rate limits. Add the domain to ssl.sh monitoring (in development), since Let's Encrypt no longer sends expiry emails.

Troubleshooting

  • Error adding TXT record or invalid token: the token lacks DNS edit permission on this zone, or the variable names are wrong; re-export and run again.
  • Verify error: no TXT record found: the record had not propagated to all authoritative servers; add --dnssleep 120 to wait longer.
  • CAA record prevents issuance: add 0 issue "letsencrypt.org" (and issuewild if you use it); see CAA record prevents issuance.
  • Rate limited: too many test issuances; use --staging and see Let's Encrypt rate limits.
  • Browser still shows the old certificate: the web server points into ~/.acme.sh/ or the reload command failed; check ~/.acme.sh/acme.sh.log.
  • Browser reports a missing intermediate: you installed --cert-file instead of --fullchain-file; see missing intermediate certificate.

FAQ

Does a wildcard certificate cover the bare domain?

No. *.example.com matches www.example.com and api.example.com but not example.com itself, and not a.b.example.com. Request both -d example.com -d '*.example.com' in one certificate.

Where does acme.sh store my DNS API token?

After the first successful issue, acme.sh saves the credentials as SAVED_... entries in ~/.acme.sh/account.conf so renewals can reuse them. Protect that file, use a token limited to DNS edit rights on the zones you need, and rotate it if the server is compromised.

What is dns-persist-01 in acme.sh?

acme.sh 3.1.4 added support for the dns-persist-01 challenge (--dns-persist-days), which validates with a long-lived TXT record bound to your ACME account instead of a new record on every order. Check your CA's announcements for whether it is available in production before relying on it.

Can I keep a ZeroSSL certificate instead?

Yes. ZeroSSL is acme.sh's default CA and requires an account email (acme.sh --register-account -m you@example.com). Your CAA records must allow the CA you use.

Sources

  1. acme.sh wiki
  2. acme.sh wiki: How to use DNS API
  3. acme.sh wiki: Profile selection
  4. acme.sh releases
  5. Let's Encrypt: Challenge Types