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.shdoes not requirerootorsudo. 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-certand 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:
- Copies
acme.shinto your home directory at~/.acme.sh/(this is also where all certificates are stored). - Creates a shell alias so you can type
acme.shfrom anywhere:acme.sh=~/.acme.sh/acme.sh. - Installs a daily
cronjob 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.shalias takes effect. Ifacme.sh: command not foundappears, 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
--nocronand/or--noprofileto the--installcommand.
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.shwrites 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:
- Export the credential environment variables for your provider.
- 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.shsaves 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-documentingOptions: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--eccto 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
--reloadcmdis 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:
- You passed
--force. - The CA's ARI (ACME Renewal Information, RFC 9773) suggested-renewal window has started.
- 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 --renewskips certs that aren't due yet and returns exit code2for "skipped". In scripts, treat2as success, or pass--treat-skip-as-successso it returns0.
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
--removestops renewal but leaves the cert and key files on disk. To delete them, remove the~/.acme.sh/example.com/directory yourself.--revoketells 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.shevolves 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.