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.comcertificates - 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 120if validation fails intermittently - Rate limits: Use
--stagingfor 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.