acme.sh
Pure shell ACME client for TLS certificates
acme.sh is an open-source ACME protocol client implemented entirely in shell script for obtaining and renewing TLS certificates. It has no dependencies and is widely self-hosted on servers and routers.
Key features
- Pure shell implementation
- No dependencies
- Many DNS provider APIs
- Automatic renewal
Pros & cons
Strengths
- Zero dependencies
- Huge DNS provider support
- Runs almost anywhere
Trade-offs
- CLI only
- Shell scripts to debug
acme.sh replaces
Last reviewed Aug 26, 2026 · 903 words
Install acme.sh today, issue a certificate, and it will come from ZeroSSL, not Let's Encrypt. The default CA changed in version 3.0 and it still catches people who have only ever used Certbot. ZeroSSL certificates work fine, but if you expected Let's Encrypt, or your other tooling assumes it, run this once before anything else:
acme.sh --set-default-ca --server letsencrypt
That single command is the difference between a 5-minute setup and a confused hour reading log files. With it out of the way, acme.sh is the most portable ACME client there is: one shell script, a cron entry, and support for well over 100 DNS providers.
It runs anywhere sh and curl do
The catalogue lists 32 MB of RAM and "no dependencies", and both are literal. acme.sh needs a POSIX shell, curl or wget, and openssl, which means it runs on OpenWrt routers, Synology and QNAP boxes, FreeBSD jails, Proxmox hosts, and the 2013 VPS you forgot about. Certbot needs a Python runtime and a pile of packages; that is the entire reason to choose acme.sh over it. Install as the user that will own the certificates:
curl https://get.acme.sh | sh -s [email protected]
It unpacks into ~/.acme.sh/, adds an alias to your shell profile, and registers a daily cron job that renews anything within 30 days of expiry. Certificates are valid for 90 days and renew at 60, so the cron job is what makes this a set-and-forget tool. If the daily run ever stops, acme.sh --list shows every certificate it manages and its next renewal date.
DNS-01 is the mode worth learning first
HTTP validation needs port 80 reachable from the internet. DNS validation needs an API token for your DNS provider and nothing else, which means it works for wildcards, for hosts on a LAN behind Tailscale, and for a mail server that has no web port at all. The DNS for self-hosters post covers picking a provider with a decent API; Cloudflare is the common case:
export CF_Token="your-scoped-api-token"
export CF_Zone_ID="your-zone-id"
acme.sh --issue --dns dns_cf -d example.com -d '*.example.com'
The credentials are saved into ~/.acme.sh/account.conf after the first successful issue, so renewals need nothing exported. Scope the token to edit DNS on one zone only; that file is plain text, and a token that can change every record you own does not belong in it. The dnsapi wiki lists the variable names for every other provider.
Never point a service at ~/.acme.sh directly
The files under ~/.acme.sh/example.com_ecc/ (ECC keys are the default now, hence the suffix) are working state, and their layout is not a stable interface. The supported pattern is --install-cert, which copies the certificate to paths you choose and records a reload command that runs after every renewal:
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"
That command is remembered, so a renewal at 3 a.m. also reloads nginx at 3 a.m. Skipping this step is the most common acme.sh failure I see: the certificate renews on schedule, the service keeps serving the old one from memory, and 30 days later it expires in production while the cron log says everything is fine.
HTTP-01 when DNS is not an option
On a public web host with no usable DNS API, --webroot /var/www/html drops the challenge file into an existing site, and --standalone spins up a temporary listener on port 80 if nothing else is using it. There is also --nginx mode, which edits the running nginx config just long enough to answer the challenge; it works, but the webroot method is easier to reason about when something goes wrong. None of these can issue a wildcard, which is the ACME protocol's rule rather than acme.sh's.
When you do not need it at all
Caddy obtains and renews certificates internally, Traefik does the same, and Nginx Proxy Manager wraps Certbot in a web form. If everything you serve sits behind one of those, adding acme.sh is a second thing to maintain for no gain; the TLS certificates done right post walks through that decision. acme.sh earns its place when the thing needing a certificate is not a modern reverse proxy: plain nginx or HAProxy, a Postfix or Dovecot mail server, a Proxmox web console, a router's admin page, or a wildcard you want to hand to several services at once.
What I'd do
Switch the default CA to Let's Encrypt, issue a wildcard with DNS-01 using a zone-scoped token, and deploy it with --install-cert plus a reload command for every consumer. Check acme.sh --list once when you set it up and once a quarter afterwards. On the box that only runs Caddy, do not install it at all. On everything else, from the NAS to the router, it is the client I have trusted for years precisely because there is so little of it to break.
Compare acme.sh
2 head-to-head comparisons.
Similar reverse proxy & gateways apps
Caddy
Reverse Proxy & GatewaysFast, multi-platform web server with automatic HTTPS
Replaces Nginx, Apache
Traefik
Reverse Proxy & GatewaysCloud-native reverse proxy and load balancer
Replaces HAProxy, AWS ELB
Pi-hole
Reverse Proxy & GatewaysBlackhole for Internet advertisements with a GUI for management
Replaces NextDNS
mitmproxy
Reverse Proxy & GatewaysInteractive HTTPS proxy for inspection and debugging
Replaces Charles Proxy, Fiddler
Kong Gateway
Reverse Proxy & GatewaysCloud-native, fast, scalable API gateway
Replaces AWS API Gateway, Apigee
Istio
Reverse Proxy & GatewaysFull-featured service mesh for traffic and security control
Replaces AWS App Mesh