acme.sh is an ACME client written in pure shell script. It needs no Python or extra dependencies, runs as a normal user, and supports well over 100 DNS providers’ APIs for DNS validation — making it the go-to tool for wildcard certificates, servers without port 80, and DNS providers Certbot has no plugin for.
Install
Install as the user that will manage certificates (root is common, but a dedicated user works too):
curl https://get.acme.sh | sh -s [email protected]
source ~/.bashrc
acme.sh --version
It installs to ~/.acme.sh/, adds a shell alias and a cron job for automatic renewal.
Choose the CA
acme.sh’s default CA has changed over time (ZeroSSL became the default at one point). Set Let’s Encrypt explicitly if that’s what you want:
acme.sh --set-default-ca --server letsencrypt
Issue a certificate: webroot mode
If a web server already serves the domain on port 80:
acme.sh --issue -d example.com -d www.example.com -w /var/www/example.com
acme.sh places the challenge file under /.well-known/acme-challenge/ in that webroot.
Other HTTP modes: --nginx or --apache (temporarily adjusts the server config), --standalone (runs its own listener on port 80 — the port must be free).
Issue a certificate: DNS API (wildcards, no port 80)
Export your DNS provider’s API credentials, then use the matching dns_* plugin. Cloudflare example (a scoped API token with Zone:DNS:Edit):
export CF_Token="your-api-token"
export CF_Zone_ID="your-zone-id" # optional, limits to one zone
acme.sh --issue --dns dns_cf -d example.com -d '*.example.com'
acme.sh saves the credentials in ~/.acme.sh/account.conf for renewals — protect that file. The acme.sh wiki lists the variables for each supported provider (Route 53, DigitalOcean, Hetzner, Gandi, Namecheap, Alibaba Cloud, DNSPod and many more).
For providers without an API, DNS alias mode lets you delegate _acme-challenge via CNAME to a zone on a supported provider.
Install the certificate into your web server
Don’t point Nginx at files inside ~/.acme.sh/ — their layout may change. Use --install-cert, which copies files to stable paths and remembers a reload command for every renewal:
sudo mkdir -p /etc/nginx/ssl/example.com
acme.sh --install-cert -d example.com \
--key-file /etc/nginx/ssl/example.com/key.pem \
--fullchain-file /etc/nginx/ssl/example.com/fullchain.pem \
--reloadcmd "systemctl reload nginx"
Nginx:
ssl_certificate /etc/nginx/ssl/example.com/fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/example.com/key.pem;
Use the fullchain file — see certificate chain errors. If acme.sh runs as a non-root user, the reload command needs a sudo rule for exactly that command.
ECDSA or RSA
acme.sh issues ECDSA (ec-256) certificates by default in recent versions — smaller and faster. For RSA:
acme.sh --issue -d example.com -w /var/www/example.com --keylength 2048
You can have both (Nginx can serve an ECDSA and an RSA certificate together for maximum compatibility).
Renewal
The installed cron job runs daily and renews certificates about 30 days before expiry (60 days after issue), then runs your reload command. Check:
crontab -l | grep acme
acme.sh --list
acme.sh --renew -d example.com --force # test a renewal now
Useful commands
acme.sh --list # certificates and renewal dates
acme.sh --info -d example.com # settings saved for a domain
acme.sh --revoke -d example.com
acme.sh --remove -d example.com # stop renewing (files stay)
acme.sh --upgrade --auto-upgrade # keep acme.sh updated
acme.sh vs Certbot
| acme.sh | Certbot | |
|---|---|---|
| Dependencies | Shell only | Python |
| DNS providers | 100+ built in | Plugins for major providers |
| Web server auto-config | Basic | Excellent for Nginx/Apache |
| Default CA | Configurable (set it!) | Let’s Encrypt |
| Typical use | Wildcards, DNS validation, minimal systems | Standard web servers |
For a normal Nginx site, Certbot is simplest. For wildcards with an unusual DNS provider, routers or minimal containers, acme.sh is often the better fit. See also wildcard certificates with Certbot.