acme.sh
Pure shell ACME client for free SSL/TLS certificates
Overview
acme.sh is a pure Unix shell (bash, dash, sh compatible) implementation of the ACME protocol client. It lets you obtain free SSL/TLS certificates from certificate authorities that support ACME, including Let's Encrypt, ZeroSSL, Buypass, SSL.com, and Google Trust Services.
Because it is written entirely in shell script, acme.sh has zero dependencies — no Python, no Go, no compiled binaries. It runs on any POSIX-compatible system with curl or wget and openssl. This makes it ideal for minimal servers, embedded systems, containers, and environments where installing a full runtime is impractical.
Core Pure Shell
Written entirely in shell script. No dependencies beyond standard Unix utilities (openssl, curl/wget, cron). Works on bash, dash, sh, zsh. Runs on Linux, macOS, FreeBSD, Solaris, and even Windows via Cygwin/WSL.
Core ACME Protocol
Implements the ACME (Automatic Certificate Management Environment) protocol defined in RFC 8555. This is the same protocol used by all major free CAs. It automates domain validation, certificate issuance, and renewal without human intervention.
Feature ~150 DNS Providers
Built-in support for over 150 DNS API providers for automated DNS-01 validation. Cloudflare, AWS Route53, Azure DNS, DigitalOcean, GoDaddy, Namecheap, and many more — all without external plugins.
Feature Multiple CAs
Not locked to a single CA. Supports Let's Encrypt, ZeroSSL (default since v3.0), Buypass, SSL.com, and Google Trust Services. Switch CAs with a single flag.
How ACME works (simplified)
The ACME protocol automates the certificate lifecycle in three steps:
- Account registration — the client creates an account with the CA and registers a public key.
- Domain validation — the CA issues a challenge to prove you control the domain. The two main challenge types are HTTP-01 (place a file at
http://yourdomain/.well-known/acme-challenge/) and DNS-01 (create a_acme-challengeTXT record in DNS). - Certificate issuance — once the challenge is verified, the CA signs and returns the certificate. The client stores it locally and can auto-renew before expiry.
acme.sh vs Certbot
Certbot and acme.sh are the two most popular ACME clients. They solve the same problem but take very different approaches.
| Aspect | acme.sh | Certbot |
|---|---|---|
| Language | Pure shell script (bash/sh) | Python |
| Dependencies | None (just openssl + curl/wget) | Python 3, pip, several Python packages |
| Root required | No — runs as regular user for DNS validation. Only needs root if binding port 80 (standalone mode). | Yes for HTTP validation (binds port 80). Can use DNS plugins without root. |
| DNS providers | ~150 built-in DNS API integrations | ~14 official DNS plugins (separate packages, plus third-party plugins) |
| Web server plugins | None — manual integration via --install-cert | nginx and Apache plugins that auto-configure TLS |
| Documentation | Good wiki, community-driven | Excellent official docs, EFF-maintained |
| Auto-renewal | cron job (installed automatically) | systemd timer or cron (installed automatically) |
| Official status | Community project | Official Let's Encrypt client (EFF) |
| Portability | Any POSIX shell system | Requires Python runtime |
| Scriptability | Excellent — designed for automation | Good, but heavier for scripting |
Choose acme.sh when you want zero dependencies, need DNS validation with a wide range of providers, run on minimal/embedded systems, or want maximum scriptability. Choose Certbot when you want automatic nginx/Apache configuration, prefer official EFF documentation, or are already in a Python ecosystem.
Issuing Certificates
acme.sh supports multiple validation methods. The right one depends on your setup: whether you have a running web server, whether you need wildcard certs, and whether you can modify DNS records programmatically.
Webroot mode
If you already have a web server (nginx, Apache, etc.) running on port 80, use webroot mode. acme.sh places the challenge file in your web root directory, and the CA verifies it over HTTP.
# Issue a cert using your existing web server's document root
acme.sh --issue -d example.com -w /var/www/html
# Multiple domains (SAN certificate)
acme.sh --issue -d example.com -d www.example.com -w /var/www/html
Standalone mode
If no web server is running, acme.sh can temporarily start its own HTTP server on port 80 to respond to the challenge. This requires port 80 to be free (and typically requires root or CAP_NET_BIND_SERVICE).
# Standalone mode — acme.sh binds port 80 temporarily
acme.sh --issue -d example.com --standalone
# Use a different port (if you have a reverse proxy forwarding port 80)
acme.sh --issue -d example.com --standalone --httpport 8080
Wildcard certificates
Wildcard certificates (*.example.com) require DNS-01 validation. HTTP validation cannot prove control over all subdomains. You must use the --dns flag with a DNS API provider.
# Wildcard cert via Cloudflare DNS API
export CF_Token="your-cloudflare-api-token"
export CF_Account_ID="your-account-id"
acme.sh --issue -d example.com -d '*.example.com' --dns dns_cf
Wildcard certs only cover one level of subdomain. *.example.com covers app.example.com but not api.staging.example.com. For deeper subdomains, you need additional wildcard entries or explicit SAN entries.
DNS Validation
DNS-01 validation proves domain ownership by creating a _acme-challenge TXT record in your domain's DNS zone. The CA queries DNS for this record to verify you control the domain. This is the only method that supports wildcard certificates and works even when ports 80/443 are blocked.
Cloudflare
# Set Cloudflare API credentials
export CF_Token="your-cloudflare-api-token"
export CF_Account_ID="your-account-id"
# Issue cert with Cloudflare DNS validation
acme.sh --issue -d example.com -d '*.example.com' --dns dns_cf
You can use either a Global API Key (CF_Key + CF_Email) or a scoped API Token (CF_Token). API Tokens are recommended — create one with Zone:DNS:Edit permission scoped to your specific zone. Use CF_Account_ID to manage multiple domains under one account, or CF_Zone_ID for a single zone. Both are optional — acme.sh can auto-detect the zone from the domain name.
Azure DNS
# Set Azure service principal credentials
export AZUREDNS_SUBSCRIPTIONID="your-subscription-id"
export AZUREDNS_TENANTID="your-tenant-id"
export AZUREDNS_APPID="your-app-id"
export AZUREDNS_CLIENTSECRET="your-client-secret"
# Issue cert with Azure DNS validation
acme.sh --issue -d example.com --dns dns_azure
The service principal needs the DNS Zone Contributor role on the DNS zone resource. Scope it narrowly to the specific zone, not the entire subscription.
DigitalOcean
# Set DigitalOcean API key
export DO_API_KEY="your-digitalocean-api-token"
# Issue cert with DigitalOcean DNS validation
acme.sh --issue -d example.com --dns dns_dgon
After the first successful issuance, acme.sh saves your DNS API credentials in ~/.acme.sh/account.conf. You do not need to export the environment variables again for renewals — acme.sh reads them from the saved config. This means automated renewals via cron work without any additional setup.
Choosing a CA
Since acme.sh v3.0, the default CA is ZeroSSL (not Let's Encrypt). While ZeroSSL works fine for most modern browsers, there are important reasons to prefer Let's Encrypt in production.
Recommended Let's Encrypt
- Certificates trusted by virtually every browser and OS released in the last 10+ years
- Excellent compatibility with older Android devices (7.1.1+), embedded systems, IoT, and corporate proxies
- Backed by ISRG (Internet Security Research Group), a nonprofit
- Massive scale: issues up to 10 million certs daily (as of late 2025)
- Well-established OCSP and CRL infrastructure
- No email required for account registration (email is optional, used for expiry notices)
Default ZeroSSL
- Default CA in acme.sh v3.0+ (requires email registration)
- Good compatibility with modern browsers
- Occasional trust issues with older Android versions and some enterprise proxy appliances
- Backed by Sectigo (commercial CA)
- Offers a web dashboard for managing certificates
- Some users report slower issuance times compared to Let's Encrypt
Setting the default CA
# Set Let's Encrypt as the default CA for all future issuances
acme.sh --set-default-ca --server letsencrypt
# Or specify per-issuance (overrides the default)
acme.sh --issue -d example.com --dns dns_cf --server letsencrypt
# Other available CAs
acme.sh --issue -d example.com --dns dns_cf --server zerossl
acme.sh --issue -d example.com --dns dns_cf --server buypass
acme.sh --issue -d example.com --dns dns_cf --server sslcom
acme.sh --issue -d example.com --dns dns_cf --server google
Run acme.sh --set-default-ca --server letsencrypt right after installing acme.sh. Let's Encrypt has the broadest trust chain and the most battle-tested infrastructure. Unless you have a specific reason to use ZeroSSL (such as needing their web dashboard or hitting Let's Encrypt rate limits), Let's Encrypt is the safer default for production.
Installing Certs
When acme.sh issues a certificate, it stores the files in ~/.acme.sh/example.com/. You should never point your applications directly at these files. acme.sh may change the file structure between versions. Instead, use the --install-cert command to copy certificates to an application-specific directory and set a reload command.
For nginx
# Install cert for nginx
acme.sh --install-cert -d example.com \
--key-file /etc/nginx/ssl/key.pem \
--fullchain-file /etc/nginx/ssl/cert.pem \
--reloadcmd "systemctl reload nginx"
The --reloadcmd is saved and re-executed automatically on every renewal. This ensures nginx picks up the new certificate without manual intervention.
For GitLab
# Install cert for GitLab (Omnibus)
acme.sh --install-cert -d gitlab.example.com \
--key-file /etc/gitlab/ssl/gitlab.example.com.key \
--fullchain-file /etc/gitlab/ssl/gitlab.example.com.crt \
--reloadcmd "gitlab-ctl reconfigure"
For generic applications
# Install cert to a custom path (e.g., for a Node.js or Java app)
acme.sh --install-cert -d app.example.com \
--key-file /opt/myapp/ssl/key.pem \
--fullchain-file /opt/myapp/ssl/cert.pem \
--reloadcmd "systemctl restart myapp"
The --install-cert command copies the cert files to the specified paths and records the configuration in ~/.acme.sh/example.com/example.com.conf. On every renewal, acme.sh automatically copies the new cert files to the same paths and runs the --reloadcmd. You set it up once and it works forever.
Auto-Renewal
acme.sh automatically installs a cron job when you first install it. This cron job runs daily and checks if any certificates are due for renewal. Certificates are renewed 30 days before expiry (Let's Encrypt certs are currently valid for 90 days, so renewal happens around day 60). Note: Let's Encrypt is transitioning to shorter certificate lifetimes — 45-day certs will be available by 2028, with opt-in starting May 2026.
Checking renewal status
# List all managed certificates with their expiry dates
acme.sh --list
# Check the installed cron job
crontab -l | grep acme.sh
# View renewal configuration for a specific domain
cat ~/.acme.sh/example.com/example.com.conf
Manual renewal
# Force renewal (even if not yet due)
acme.sh --renew -d example.com --force
# Renew all certificates
acme.sh --renew-all
# Force renew all
acme.sh --renew-all --force
The cron job runs acme.sh --cron once per day (typically at a random minute to avoid thundering herd). This command iterates over all certificates in ~/.acme.sh/, checks if any are within 30 days of expiry, and renews those that are. After renewal, it runs the --reloadcmd from --install-cert to reload the application. No human interaction required.
Troubleshooting renewals
# Test renewal against the CA's staging server (issues an untrusted cert)
acme.sh --renew -d example.com --force --test
# Enable debug logging for renewal
acme.sh --renew -d example.com --force --debug 2
# Check acme.sh logs
cat ~/.acme.sh/acme.sh.log
Production Checklist
- Set default CA to Let's Encrypt — run
acme.sh --set-default-ca --server letsencryptimmediately after installation. - Use DNS validation for wildcards — HTTP validation cannot issue wildcard certs. Set up DNS API credentials for your provider.
- Use
--install-cert— never point applications directly at~/.acme.sh/files. Always install to a dedicated path with a reload command. - Verify the cron job exists — run
crontab -l | grep acme.shto confirm automatic renewal is configured. - Test renewal before going live — run
acme.sh --renew -d example.com --force --testto verify the renewal pipeline works end to end using the CA's staging server (issues an untrusted cert, but validates the full flow). - Scope DNS API credentials narrowly — use API tokens with minimal permissions (e.g., Cloudflare: Zone:DNS:Edit on a single zone). Never use global admin keys.
- Protect
~/.acme.sh/— this directory contains private keys and API credentials. Restrict permissions to the owning user (chmod 700 ~/.acme.sh). - Monitor certificate expiry — set up external monitoring (e.g.,
ssl-cert-check, Uptime Kuma, or Prometheus blackbox exporter) to alert if a cert gets within 14 days of expiry. - Set a notification email — register with
acme.sh --register-account -m admin@example.comso the CA can notify you of expiry or policy changes. - Keep acme.sh updated — run
acme.sh --upgradeperiodically. ACME protocol and CA endpoints evolve. - Use ECC certificates — since acme.sh v3.0.6,
ec-256(ECDSA P-256) is the default key type, so new issuances already use ECC. For older installations or explicit control, add--keylength ec-256. ECC certs offer smaller, faster TLS handshakes than RSA and are supported by all modern clients. - Back up account keys — the files in
~/.acme.sh/ca/contain your ACME account registration. Back them up to avoid having to re-register.