Managing SSL certificates manually is tedious and error-prone. Here’s how I set up fully automated certificate management using acme.sh with Cloudflare DNS validation.

Why acme.sh + DNS-01?

  • No port 80 required: DNS-01 validation doesn’t need a running web server or open HTTP port
  • Wildcard support: Can issue *.example.com certificates
  • Cloudflare integration: API-based, fully automated
  • Lightweight: Pure shell script, no dependencies

Installation

curl https://get.acme.sh | sh -s email=you@example.com
source ~/.bashrc

Cloudflare API Token

Create a token at Cloudflare Dashboard with these permissions:

  • Zone - DNS - Edit (for the specific zone)
  • Zone - Zone - Read

Export the credentials:

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

Issue a Certificate

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

The ec-256 flag requests an ECDSA P-256 certificate, which is smaller and faster than RSA.

Install the Certificate

Don’t use the files in ~/.acme.sh/ directly. Use the install command to copy them to the right location:

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

Auto-Renewal

acme.sh sets up a cron job automatically. Verify:

crontab -l | grep acme

You should see something like:

0 0 * * * "/root/.acme.sh"/acme.sh --cron --home "/root/.acme.sh" > /dev/null

Certificates are renewed 30 days before expiry.

Troubleshooting

  • DNS propagation delay: Add --dnssleep 120 if validation fails intermittently
  • Rate limits: Use --staging for testing to avoid Let’s Encrypt rate limits
  • Check certificate: echo | openssl s_client -connect example.com:443 2>/dev/null | openssl x509 -noout -dates

This setup has been running on my servers for months without any manual intervention. One less thing to worry about.