Jesse A. Green / notebook

acme.sh User Guide

Generated June 2026 from acmesh-official/acme.sh at 3c848ad

AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it.

acme.sh is an ACME protocol client written purely in Unix shell. It issues, renews, and installs free TLS/SSL certificates from ZeroSSL, Let's Encrypt, Google, SSL.com, Actalis, and any RFC 8555-compliant Certificate Authority (CA) — with no dependency on Python, Go, or any compiled runtime. If you have a POSIX shell, curl (or wget), openssl, and cron, you can run it.

This guide takes you from a clean machine to fully automated, self-renewing certificates. Written from the acme.sh source.

Note

acme.sh does not require root or sudo. It installs entirely inside your home directory (~/.acme.sh/) and renews certificates as your own user. Running as root is supported and is only needed for privileged modes (standalone on port 80/443, Apache/Nginx integration, writing certs into system paths).


1. Quick Start

Three commands take you from nothing to a live, auto-renewing certificate using the webroot of an existing website.

# 1. Install acme.sh into ~/.acme.sh and register an account
curl https://get.acme.sh | sh -s email=[email removed]

# 2. Open a new shell so the `acme.sh` alias is available, then issue a cert
acme.sh --issue -d example.com -w /var/www/html

# 3. Copy the cert into your web server and tell acme.sh how to reload it
acme.sh --install-cert -d example.com \
  --key-file       /etc/nginx/ssl/example.com.key \
  --fullchain-file /etc/nginx/ssl/example.com.cer \
  --reloadcmd      "service nginx force-reload"
[Wed Jun  4 00:00:00 UTC 2026] Cert success.
[Wed Jun  4 00:00:00 UTC 2026] Your cert is in: /root/.acme.sh/example.com/example.com.cer
[Wed Jun  4 00:00:00 UTC 2026] Installing key to: /etc/nginx/ssl/example.com.key
[Wed Jun  4 00:00:00 UTC 2026] Running reload cmd: service nginx force-reload
[Wed Jun  4 00:00:00 UTC 2026] Reload successful

That is the whole lifecycle. The installer also created a daily cron job, so the certificate renews itself indefinitely — you never have to touch it again.

Important

Always deliver the certificate to your server with --install-cert and a --reloadcmd. Do not point your web server directly at the files in ~/.acme.sh/. Those are internal working files; their layout can change, and a copy there will silently go stale after a renewal.


2. Installation

2.1 Requirements

Requirement Notes
POSIX shell bash, dash, ash, or plain sh. Detected and used automatically.
openssl The cryptographic workhorse — key generation, CSR, signing. Required.
curl or wget For ACME / DNS API HTTP calls. At least one is required.
socat Only for standalone mode (built-in web server). Not needed otherwise.
crontab For automatic renewal. Optional but strongly recommended.
idn Only if you issue certs for Internationalized Domain Names (IDN).

acme.sh is tested on Linux (Debian, Ubuntu, Alpine, Arch, Fedora, openSUSE, Gentoo, Kali, Oracle Linux, Mageia, and more), macOS, FreeBSD, OpenBSD, NetBSD, DragonFlyBSD, Solaris/illumos, Windows (Cygwin), Haiku, and OpenWRT.

2.2 Install methods

Install online (recommended)

curl https://get.acme.sh | sh -s email=[email removed]

Or with wget:

wget -O - https://get.acme.sh | sh -s email=[email removed]

Install from Git

git clone https://github.com/acmesh-official/acme.sh.git
cd ./acme.sh
./acme.sh --install -m [email removed]

Run in Docker

docker run --rm -it \
  -v "$(pwd)/acme.sh-data":/acme.sh \
  neilpang/acme.sh --version

The installer performs exactly three actions:

  1. Copies acme.sh into your home directory at ~/.acme.sh/ (this is also where all certificates are stored).
  2. Creates a shell alias so you can type acme.sh from anywhere: acme.sh=~/.acme.sh/acme.sh.
  3. Installs a daily cron job that checks and renews certificates as needed:
    0 0 * * * "/home/user/.acme.sh"/acme.sh --cron --home "/home/user/.acme.sh" > /dev/null
    

Warning

After installing, close your terminal and open a new one so the acme.sh alias takes effect. If acme.sh: command not found appears, this is almost always the cause.

2.3 Verify the install

acme.sh --version
https://github.com/acmesh-official/acme.sh
v3.1.4
acme.sh -h        # full list of commands and parameters

Tip

Prefer not to install the cron job or shell alias (e.g. you manage scheduling yourself)? Add --nocron and/or --noprofile to the --install command.


3. Choosing a Validation Mode

Before issuing a certificate, the CA must verify you control the domain. acme.sh supports every standard ACME challenge plus several convenience wrappers. Pick the row that matches your situation.

Mode Flag Needs root? Best for
Webroot -w <dir> No A server already serving a website over HTTP
Standalone --standalone Yes (port 80) A box with no web server running
Standalone ALPN --alpn Yes (port 443) Port 80 blocked, port 443 free
Apache --apache Yes An Apache server (no config edits made)
Nginx --nginx Yes An Nginx server (config auto-restored after)
DNS API --dns <hook> No Wildcards; servers with no public HTTP; fully automatic
DNS manual --dns No A DNS provider with no API (manual TXT entry)
DNS persist --dns-persist No Unattended renewal without DNS API (single long-lived record)
Stateless --stateless No Behind a reverse proxy / many front-ends

Tip

For wildcard certificates (*.example.com) you must use a DNS-based mode — HTTP challenges cannot prove control of a wildcard. DNS API mode is the only one that renews wildcards with zero manual work.


4. Issuing Certificates

The certificate's main domain (the first -d) doubles as its name — you'll use it later to renew, install, or remove the cert. Certificates default to ECDSA P-256 (ec-256) keys and are stored under ~/.acme.sh/<main-domain>/.

4.1 Webroot mode

Point -w at the document root of a site you already serve over HTTP on port 80.

acme.sh --issue -d example.com -w /var/www/html

Add more -d flags for a multi-domain (SAN) certificate. All domains must resolve to this same server and share the webroot:

acme.sh --issue \
  -d example.com -d www.example.com -d cp.example.com \
  -w /var/www/html

Note

You must have write access to the webroot directory — acme.sh writes a temporary challenge file under <webroot>/.well-known/acme-challenge/ and removes it afterward.

4.2 Standalone mode

When nothing is serving HTTP, let acme.sh run its own temporary web server. Requires socat and the ability to bind the port.

# Temporary server on port 80 (HTTP-01)
acme.sh --issue --standalone -d example.com

# Temporary server on port 443 (TLS-ALPN-01) — use when port 80 is blocked
acme.sh --issue --alpn -d example.com

If your machine sits behind a load balancer or reverse proxy that forwards a non-standard port, set the listening port explicitly:

acme.sh --issue --standalone -d example.com --httpport 8080

4.3 Apache / Nginx mode

If you run Apache or Nginx, these modes verify the domain without writing to your webroot and without permanently changing your server config — Nginx mode temporarily reconfigures the server and restores it afterward.

acme.sh --issue --apache -d example.com
acme.sh --issue --nginx  -d example.com

Important

Apache/Nginx mode only issues the certificate — it does not wire the cert into your site. You still configure your vhost to use the cert (see §6, Installing the Certificate).

4.4 DNS API mode (fully automatic, supports wildcards)

If your DNS provider has an API, acme.sh can create and delete the validation TXT records for you — including for wildcards — and do it again automatically at every renewal. This is the recommended mode for most modern setups.

The pattern is always the same:

  1. Export the credential environment variables for your provider.
  2. Run --issue --dns <hookname>.

Example for Cloudflare (dns_cf):

export CF_Token="your-cloudflare-api-token"
export CF_Account_ID="your-account-id"

acme.sh --issue --dns dns_cf -d example.com -d '*.example.com'
[Wed Jun  4 00:00:00 UTC 2026] Adding txt value for domain: _acme-challenge.example.com
[Wed Jun  4 00:00:00 UTC 2026] Added, OK
[Wed Jun  4 00:00:00 UTC 2026] Let's check the txt records, DNS is propagating...
[Wed Jun  4 00:00:00 UTC 2026] Cert success.

Tip

You only need to export the credentials once. On first use, acme.sh saves them into its account configuration (~/.acme.sh/account.conf) so renewals run unattended — you do not have to re-export them in the cron environment.

acme.sh ships 178 DNS provider plugins. A few of the most common, with their required variables:

Provider Hook Required environment variables
Cloudflare dns_cf CF_Token + CF_Account_ID (or legacy CF_Key + CF_Email)
AWS Route 53 dns_aws AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY
Google Cloud DNS dns_gcloud (uses gcloud CLI auth)
Azure DNS dns_azure AZUREDNS_SUBSCRIPTIONID, AZUREDNS_TENANTID, AZUREDNS_APPID, AZUREDNS_CLIENTSECRET
DigitalOcean dns_dgon DO_API_KEY
GoDaddy dns_gd GD_Key, GD_Secret
Namecheap dns_namecheap NAMECHEAP_API_KEY, NAMECHEAP_USERNAME, NAMECHEAP_SOURCEIP
DuckDNS dns_duckdns DuckDNS_Token

Note

To find the exact variables for any provider, open its plugin file dnsapi/dns_<provider>.sh — each begins with a self-documenting Options: block listing every variable it reads. Full per-provider documentation lives at the dnsapi wiki.

4.5 DNS manual mode

If your provider has no API, you can add the TXT records by hand. Note the warning: manual mode cannot auto-renew.

acme.sh --issue --dns -d example.com -d www.example.com
Add the following txt record:
Domain:_acme-challenge.example.com
Txt value:9ihDbjYfTExAYeDs4DBUeuTo18KBzwvTEjUnSwd32-c

Please add those txt records to the domains. Waiting for the dns to take effect.

Add each TXT record at your DNS provider, wait for propagation, then complete issuance:

acme.sh --renew -d example.com

Caution

DNS manual mode is a one-time, hands-on operation. Every renewal requires you to add a fresh TXT record by hand. For unattended renewal without an API, use DNS persist mode (§4.6) instead.

4.6 DNS persist mode

DNS persist mode (an implementation of draft-ietf-acme-dns-persist-01) lets you place one long-lived _validation-persist TXT record in your zone and reuse it for every future issuance and renewal — no per-issuance token, so renewals need no DNS edits.

# Step 1: print the TXT record to add (and pick a CA)
acme.sh --make-dns-persist-value -d example.com --server letsencrypt
TXT persist domain:_validation-persist.example.com
TXT persist value :"letsencrypt.org; accounturi=https://acme-v02.api.letsencrypt.org/acme/acct/123456789"

Add that single TXT record at your DNS provider, wait for propagation, then issue:

# Step 2: issue using the persistent record
acme.sh --issue -d example.com --dns-persist

Optional flags for --make-dns-persist-value:

Flag Effect
--dns-persist-wildcard Authorizes wildcard / subdomain certificates from the same record.
--dns-persist-ca-name <name> Pin a specific CA identity domain (e.g. ssl.com).
--dns-persist-days <N> Self-expire the record N days from now.

4.7 Choosing the key type (ECC or RSA)

The default is ec-256. Override with -k / --keylength:

Value Key
ec-256 ECDSA P-256 — default, fast and widely supported
ec-384 ECDSA P-384
ec-521 ECDSA P-521 (not accepted by Let's Encrypt)
2048 / 3072 / 4096 / 8192 RSA of that bit length
acme.sh --issue -d example.com -w /var/www/html --keylength 4096   # RSA 4096

Tip

You can issue both an ECC and an RSA certificate for the same domain. They are stored separately; the RSA/ECC cert lives in ~/.acme.sh/example.com_ecc/ vs ~/.acme.sh/example.com/. Add --ecc to later commands to act on the ECC copy.


5. Selecting a Certificate Authority

ZeroSSL is the default CA. Switch per-issuance with --server, or set a new global default.

# Issue from Let's Encrypt for this cert only
acme.sh --issue -d example.com -w /var/www/html --server letsencrypt

# Change the default CA for all future issuances
acme.sh --set-default-ca --server letsencrypt
CA --server value Notes
ZeroSSL zerossl Default. May require an EAB-bound account (handled automatically when an email is registered).
Let's Encrypt letsencrypt
Let's Encrypt (staging) letsencrypt_test Testing only — issues untrusted certs. Equivalent to --staging.
Google Public CA google Requires EAB credentials.
SSL.com sslcom
Actalis actalis

For CAs that require External Account Binding (EAB), pass the credentials once at registration:

acme.sh --register-account --server google \
  --eab-kid  <your-key-id> \
  --eab-hmac-key <your-hmac-key>

Tip

When experimenting, always add --staging (or --server letsencrypt_test). Production CAs enforce strict rate limits; the staging environment does not, so you can iterate freely without getting blocked.


6. Installing the Certificate Into Your Server

Issuing a certificate only writes it into ~/.acme.sh/. The --install-cert command copies it to wherever your server expects it and runs a reload command. This is also what wires up automatic reload-on-renewal — acme.sh remembers these paths and re-runs the reload command every time the cert renews.

acme.sh --install-cert -d example.com \
  --cert-file      /path/to/cert.pem \
  --key-file       /path/to/key.pem \
  --fullchain-file /path/to/fullchain.pem \
  --reloadcmd      "service nginx force-reload"
Flag What it receives
--cert-file The leaf (domain) certificate only
--key-file The private key
--ca-file The intermediate (CA) certificate only
--fullchain-file Leaf + intermediate concatenated (what most servers want)
--reloadcmd Shell command run after install/renew (e.g. reload your web server)

Only -d is required; supply whichever file paths your server needs.

Warning

The --reloadcmd is the most important argument here. A certificate can renew perfectly, but without a correct reload command your running server keeps serving the old cert from memory until it is restarted. Test your reload command by hand once before trusting it.

6.1 Deploy hooks (push to services beyond a local file)

For targets that aren't just "a file on disk + reload" — Docker containers, routers, load balancers, panels, remote hosts over SSH — use a deploy hook. acme.sh includes 52 deploy hooks. Each reads DEPLOY_<NAME>_* environment variables.

Example: deploy into a running Docker container by label:

export DEPLOY_DOCKER_CONTAINER_LABEL="sh.acme.autoload.domain=example.com"
export DEPLOY_DOCKER_CONTAINER_RELOAD_CMD="service nginx force-reload"

acme.sh --deploy -d example.com --deploy-hook docker

A few common hooks:

Target Hook name
Remote host over SSH ssh
Docker container docker
Nginx nginx
HAProxy haproxy
Synology DSM synology_dsm
Proxmox VE proxmoxve
HashiCorp Vault vault

Note

Like DNS credentials, deploy-hook variables are saved on first use and reused at every renewal, so the deployment repeats automatically. See the deploy hooks wiki for every hook and its variables.


7. Renewing Certificates

You normally never renew manually — the cron job installed at setup runs acme.sh --cron daily and renews anything that is due.

A certificate is renewed when any of these is true:

  1. You passed --force.
  2. The CA's ARI (ACME Renewal Information, RFC 9773) suggested-renewal window has started.
  3. The cached next-renewal time (default: 30 days after issuance) has passed.

ARI support is automatic and needs no flag: when the CA advertises a renewalInfo endpoint (Let's Encrypt, ZeroSSL, …), acme.sh polls it every cron run and renews early if the CA asks (e.g. during a mass-revocation event).

To force a renewal now:

acme.sh --renew -d example.com --force
acme.sh --renew -d example.com --force --ecc   # the ECC copy

Renew everything at once (this is what cron does):

acme.sh --renew-all

See when a cert is next due:

acme.sh --info -d example.com | grep Le_NextRenewTimeStr

Tip

acme.sh --renew skips certs that aren't due yet and returns exit code 2 for "skipped". In scripts, treat 2 as success, or pass --treat-skip-as-success so it returns 0.


8. Renewal Notifications

Get an email or chat message whenever the cron job renews (or fails to renew) a cert. acme.sh includes 26 notification hooks (Slack, Telegram, Discord, Mattermost, Teams, email/SMTP, Pushover, Gotify, and more).

# Configure a Slack notification
export SLACK_WEBHOOK_URL="https://hooks.slack.com/services/XXX/YYY/ZZZ"
acme.sh --set-notify --notify-hook slack

A test message is sent immediately to confirm it works. Control when you're notified:

acme.sh --set-notify --notify-level 2   # 0=off 1=errors 2=renewals+errors 3=+skips
acme.sh --set-notify --notify-mode 0    # 0=one bulk message  1=one per cert

9. Managing Certificates

Task Command
List all certs acme.sh --list
List in raw/parseable form acme.sh --list --listraw
Show a cert's config acme.sh --info -d example.com
Stop renewing a cert acme.sh --remove -d example.com
Revoke a cert acme.sh --revoke -d example.com
Export to PKCS#12 (.pfx) acme.sh --to-pkcs12 -d example.com --password mypass
Convert key to PKCS#8 acme.sh --to-pkcs8 -d example.com
acme.sh --list
Main_Domain  KeyLength  SAN_Domains       CA            Created               Renew
example.com  "ec-256"   www.example.com   ZeroSSL.com   2026-06-04T00:00:00Z  2026-07-04T00:00:00Z

Note

--remove stops renewal but leaves the cert and key files on disk. To delete them, remove the ~/.acme.sh/example.com/ directory yourself. --revoke tells the CA to invalidate the cert (use it if a key is compromised).


10. Upgrading acme.sh

acme.sh --upgrade                    # update to the latest release now
acme.sh --upgrade --auto-upgrade     # enable automatic upgrades during cron
acme.sh --upgrade --auto-upgrade 0   # disable automatic upgrades

Tip

acme.sh evolves quickly and CAs change their requirements over time. Keeping auto-upgrade on is the simplest way to avoid renewal surprises.


11. Troubleshooting

Run any failing command again with --debug 2 (or --debug 3 for maximum detail) to see exactly what acme.sh and the CA exchanged.

acme.sh --issue -d example.com -w /var/www/html --debug 2

Error: Unknown parameter: <flag>

Cause: A flag is misspelled, or belongs to a newer version than you have installed. Solution: Check acme.sh -h for the exact spelling, then acme.sh --upgrade to get the latest flags. Prevention: Keep auto-upgrade enabled.

Error: 'dns_<provider>' does not contain 'dns'

Cause: The --dns hook name was given without its dns_ prefix (e.g. --dns servercow instead of --dns dns_servercow). Solution: Always use the full plugin name, including the prefix: --dns dns_servercow. Prevention: Copy the hook name from the dnsapi wiki or the plugin filename.

Error: You didn't specify a Cloudflare api key and email yet. (or similar per-provider message)

Cause: The provider's credential environment variables aren't set in the current shell. Solution: export the variables listed in that provider's plugin (e.g. CF_Token), then re-run --issue. They are saved after the first successful run. Prevention: Set credentials before the first issuance; they persist for renewals automatically.

Error: Verify error:Invalid response from http://example.com/.well-known/acme-challenge/...

Cause: The CA could not fetch the challenge file — wrong webroot, the domain doesn't resolve to this server, or a redirect/firewall is interfering. Solution: Confirm -w points at the directory actually served at http://example.com/, that the domain's DNS A/AAAA record points here, and that port 80 is reachable from the internet. Prevention: Test by manually placing a file in <webroot>/.well-known/acme-challenge/ and fetching it over HTTP before issuing.

Error: Standalone mode failed / Could not bind to port 80

Cause: Another process already holds port 80, or you lack permission to bind it. Solution: Stop the conflicting service, run as root, or use --alpn (port 443) or --httpport to choose a forwarded port. Prevention: Use webroot or DNS mode on servers that already run a web server.

Error: It seems that you are using sudo, please read this page first

Cause: acme.sh detected it was launched through sudo in a way that would store certs under the wrong home directory. Solution: Run as the target user directly (e.g. su - acmeuser), or pass --force if you intentionally want the current home. See the sudo wiki. Prevention: Install and run acme.sh consistently as one dedicated user.

Error: certificate renewed but the website still serves the old one

Cause: No --reloadcmd was set, or it doesn't actually reload the live server. Solution: Re-run --install-cert with a correct --reloadcmd, then test it by hand. Prevention: Always validate the reload command when you first install the cert.

Issue: --list shows the wrong "Renew" date with short-lived profiles

Cause: A known display quirk when using --cert-profile shortlived; the stored next-renew time can lag the actual ARI window. Solution: Trust acme.sh --info -d example.com (Le_NextRenewTimeStr) and the cron behavior over the --list column. Upgrade to the latest version.


12. Frequently Asked Questions

Q: Do I need to be root? No. acme.sh installs into your home directory and runs as your user. Root is only needed for standalone mode (binding port 80/443), Apache/Nginx mode, or writing certs into root-owned system paths.

Q: Where are my certificates stored? Under ~/.acme.sh/<main-domain>/. The files are <domain>.key (private key), <domain>.cer (leaf cert), ca.cer (intermediate), and fullchain.cer. Don't point servers at these directly — use --install-cert.

Q: How often do certs renew? Every 30 days by default, or earlier if the CA's ARI window opens. The daily cron job handles it automatically.

Q: Which CA is used by default? ZeroSSL. Use --server letsencrypt (per cert) or --set-default-ca --server letsencrypt to change it.

Q: Can I get a wildcard certificate? Yes — use a DNS-based mode. acme.sh --issue -d example.com -d '*.example.com' --dns dns_cf. HTTP challenges cannot validate wildcards.

Q: ECC or RSA — which should I pick? ECC (ec-256, the default) is smaller and faster and is supported by all modern clients. Choose RSA (2048/4096) only if you must support very old clients. You can issue both for the same domain.

Q: How do I test without hitting rate limits? Add --staging (or --server letsencrypt_test). Staging issues untrusted certs but has generous limits, so you can iterate freely.

Q: My credentials — do I have to export them every time? No. The first successful run saves DNS and deploy-hook variables into ~/.acme.sh/account.conf (or the per-domain config), and renewals reuse them automatically.

Q: How do I move acme.sh and my certs to another server? Copy the entire ~/.acme.sh/ directory to the new machine and reinstall the cron job with acme.sh --install-cronjob. All certs, keys, account data, and saved credentials travel with the directory.

Q: Does acme.sh modify my Apache/Nginx config? No. Apache/Nginx modes only use the server to answer the challenge and restore any temporary changes. You wire the issued cert into your vhost yourself (via --install-cert).

Q: How do I completely uninstall it? acme.sh --uninstall removes the alias and cron job. Your certs and keys remain in ~/.acme.sh/ — delete that directory by hand if you also want to remove them.