| Jackie Sung | Linux | 5 min read
Issue and Auto-Renew a Let's Encrypt Wildcard SSL Certificate with acme.sh
Use acme.sh with DNS-01 validation to issue and auto-renew wildcard Let's Encrypt certificates through Cloudflare, DNSPod, or Tencent Cloud DNSPod APIs.

Wildcard certificates need DNS validation. That is the whole trick: instead of putting a file on your web server, the ACME client creates a temporary TXT record such as _acme-challenge.example.com, Let’s Encrypt checks it, and the record is removed afterward.
This is an updated version of my old acme.sh note. The big change is that Cloudflare should now use a scoped API Token instead of the old Global API Key, and DNSPod users have two practical paths: the classic DNSPod token or the newer Tencent Cloud DNSPod API.
What This Setup Does
We will:
- install acme.sh;
- tell it to use Let’s Encrypt;
- issue
example.comand*.example.com; - use DNS API credentials for Cloudflare or DNSPod;
- install the certificate into an NGINX-friendly path;
- let acme.sh renew automatically.
Replace example.com everywhere with your own domain.
Install acme.sh
On Debian or Ubuntu:
sudo apt update
sudo apt install -y curl socat cron ca-certificatesOn CentOS, RHEL, AlmaLinux, or Rocky Linux:
sudo dnf install -y curl socat cronie ca-certificates
sudo systemctl enable --now crondFor this guide, run acme.sh as root. The final certificate files live under /etc/ssl, and the renewal job needs to reload NGINX after installing a fresh certificate.
sudo -iInstall acme.sh with your email address:
curl https://get.acme.sh | sh -s email=admin@example.comReload your shell or call acme.sh directly:
source ~/.bashrc
~/.acme.sh/acme.sh --versionacme.sh can use several certificate authorities. Because this article is about Let’s Encrypt, set it explicitly:
~/.acme.sh/acme.sh --set-default-ca --server letsencryptYou can also keep the default CA unchanged and add --server letsencrypt to every issue command.
Option A: Cloudflare API Token
This is the recommended Cloudflare path. Create a Cloudflare API Token with the smallest permissions you can use:
Zone -> DNS -> Edit- limited to the zone you are issuing for, such as
example.com
Then set the token and either the Zone ID or Account ID. Zone ID is simplest when you are only issuing for one domain.
export CF_Token="your_cloudflare_api_token"
export CF_Zone_ID="your_cloudflare_zone_id"Issue an ECDSA wildcard certificate:
~/.acme.sh/acme.sh --issue \
--server letsencrypt \
--dns dns_cf \
-d example.com \
-d '*.example.com' \
--keylength ec-256If you manage several zones under the same account, use CF_Account_ID instead:
export CF_Token="your_cloudflare_api_token"
export CF_Account_ID="your_cloudflare_account_id"
~/.acme.sh/acme.sh --issue \
--server letsencrypt \
--dns dns_cf \
-d example.com \
-d '*.example.com' \
--keylength ec-256The old Cloudflare CF_Key and CF_Email method still exists, but it uses the Global API Key and is not recommended unless you have a specific legacy reason.
Option B: DNSPod.cn API
For the classic DNSPod.cn API, create a DNSPod Token in your DNSPod account settings. acme.sh expects the token ID and token key as DP_Id and DP_Key.
export DP_Id="your_dnspod_token_id"
export DP_Key="your_dnspod_token_key"Issue the wildcard certificate:
~/.acme.sh/acme.sh --issue \
--server letsencrypt \
--dns dns_dp \
-d example.com \
-d '*.example.com' \
--keylength ec-256This is the shortest path if your domain is still managed directly inside DNSPod.cn.
Option C: Tencent Cloud DNSPod API
If your DNSPod zone is managed through Tencent Cloud, use the Tencent Cloud DNSPod plugin instead.
Create an API key in Tencent Cloud CAM, then export:
export Tencent_SecretId="your_tencent_secret_id"
export Tencent_SecretKey="your_tencent_secret_key"Issue the certificate with dns_tencent:
~/.acme.sh/acme.sh --issue \
--server letsencrypt \
--dns dns_tencent \
-d example.com \
-d '*.example.com' \
--keylength ec-256Use this path when your DNS records live in Tencent Cloud DNSPod rather than the older standalone DNSPod account.
Install the Certificate for NGINX
Create a stable directory for the live certificate files:
mkdir -p /etc/ssl/acme/example.comInstall the ECDSA certificate:
~/.acme.sh/acme.sh --install-cert \
-d example.com \
--ecc \
--key-file /etc/ssl/acme/example.com/privkey.pem \
--fullchain-file /etc/ssl/acme/example.com/fullchain.pem \
--reloadcmd "systemctl reload nginx"Then point NGINX at those files:
server {
listen 443 ssl http2;
server_name example.com *.example.com;
ssl_certificate /etc/ssl/acme/example.com/fullchain.pem;
ssl_certificate_key /etc/ssl/acme/example.com/privkey.pem;
root /var/www/example.com;
}Test and reload:
nginx -t
systemctl reload nginxHow Auto-Renewal Works
After the first successful issue, acme.sh saves the DNS API variables it used in ~/.acme.sh/account.conf. That means future renewals do not need you to export CF_Token, DP_Id, or Tencent_SecretId again.
The installer normally creates a cron job. Check it:
crontab -l | grep acme.shYou can test the renewal flow without waiting:
~/.acme.sh/acme.sh --cron --home ~/.acme.sh --forceDuring renewal, acme.sh will:
- create the DNS TXT record through the provider API;
- wait for DNS propagation;
- renew the certificate if needed;
- install the renewed files to the paths from
--install-cert; - run the stored
--reloadcmd.
If you prefer systemd timers instead of cron, create a oneshot service and timer.
/etc/systemd/system/acme_letsencrypt.service:
[Unit]
Description=Renew certificates using acme.sh
After=network-online.target nss-lookup.target
[Service]
Type=oneshot
ExecStart=/root/.acme.sh/acme.sh --cron --home /root/.acme.sh/etc/systemd/system/acme_letsencrypt.timer:
[Unit]
Description=Daily acme.sh renewal check
[Timer]
OnCalendar=daily
RandomizedDelaySec=1h
Persistent=true
[Install]
WantedBy=timers.targetEnable it:
systemctl daemon-reload
systemctl enable --now acme_letsencrypt.timer
systemctl list-timers | grep acmeIf you use the systemd version, disable the cron entry to avoid two schedulers doing the same job.
Useful Commands
List certificates:
~/.acme.sh/acme.sh --listForce renew one certificate:
~/.acme.sh/acme.sh --renew -d example.com --ecc --forceShow debug output while issuing:
~/.acme.sh/acme.sh --issue \
--server letsencrypt \
--dns dns_cf \
-d example.com \
-d '*.example.com' \
--keylength ec-256 \
--debug 2Small Safety Notes
- Prefer Cloudflare API Tokens over the Global API Key.
- Keep DNS API credentials out of shell history if you are sharing the server.
- Make sure
~/.acme.sh/account.confis readable only by the issuing user. - If you run acme.sh as a non-root user, keep the certificate target path writable and grant only the exact reload permission you need.
- Use
--stagingwhile testing many times, then remove it for the real certificate. - Keep the DNS provider account protected with 2FA.
