# acme.sh Administrator & Architecture Guide

This guide is for operators, system architects, and developers who deploy `acme.sh` at scale, integrate it into infrastructure, or want to understand exactly how it works internally. It is the source-based companion to the [User Guide](acmesh-official__acme.sh__UserGuide.md), which covers day-to-day certificate operations.

Everything here is derived from the `acme.sh` source at the commit stamped in the footer (version `3.1.4`).

---

## 1. Executive Summary

`acme.sh` is a complete [ACME (RFC 8555)](https://datatracker.ietf.org/doc/html/rfc8555) certificate-management client implemented entirely in POSIX shell. It has **zero compiled dependencies** — no Python, Go, Java, or Node runtime — relying only on a shell interpreter, `openssl`, an HTTP client (`curl` or `wget`), and `cron`. This makes it uniquely portable: it runs on Linux, every major BSD, macOS, Solaris/illumos, Windows (Cygwin), Haiku, and embedded targets like OpenWRT.

| Attribute | Value |
|---|---|
| Language | Unix shell (POSIX `sh`; auto-upgrades to `bash` if present) |
| Version | `3.1.4` |
| License | GPLv3 |
| Core engine | Single script `acme.sh` (~8,660 lines) |
| Extension plugins | 178 DNS providers, 52 deploy hooks, 26 notify hooks |
| Runtime dependencies | `openssl`, `curl`/`wget`, POSIX shell; `socat` (standalone), `cron`, `logger` (syslog), `idn` (IDN) optional |
| Persistent state | Flat files under `~/.acme.sh/` — no database |
| Default CA | ZeroSSL |
| Default key | ECDSA P-256 (`ec-256`) |
| Privilege model | Runs unprivileged; root only for privileged modes |
| Supported CAs | ZeroSSL, Let's Encrypt, Google Public CA, SSL.com, Actalis, any RFC 8555 CA |

**Architectural philosophy:** one self-contained script that "speaks ACME in shell," extended by a flat directory of single-purpose plugin scripts loaded on demand and dispatched purely by naming convention. There is no service, no daemon, and no central state store — every invocation reads flat config files, does its work, and exits. Scheduling is delegated to `cron`.

---

## 2. Architecture

### 2.1 Component overview

```
                        ┌──────────────────────────────────────────────┐
                        │                 acme.sh (single script)        │
                        │                                                │
  CLI / cron ─────────▶ │  main() ─▶ _process()  (argument parser)       │
                        │                │                               │
                        │                ▼                               │
                        │        command dispatch (case "$_CMD")         │
                        │   issue · renew · deploy · install-cert · …    │
                        │                │                               │
                        │   ┌────────────┼────────────┬──────────────┐   │
                        │   ▼            ▼             ▼              ▼   │
                        │ ACME core   crypto       HTTP client    config │
                        │ (RFC 8555)  (openssl     (_get/_post    (flat- │
                        │  JWS, nonce, wrappers)    via curl/wget) file)  │
                        └───────┬───────────┬───────────┬────────────────┘
                                │           │           │
              loaded on demand  │           │           │  loaded on demand
              via `. hookfile`  ▼           ▼           ▼  via `. hookfile`
                    ┌───────────────┐ ┌───────────┐ ┌──────────────┐
                    │ dnsapi/       │ │ deploy/   │ │ notify/      │
                    │ dns_*.sh ×178 │ │ *.sh ×52  │ │ *.sh ×26     │
                    │ _add / _rm    │ │ _deploy   │ │ _send        │
                    └───────┬───────┘ └─────┬─────┘ └──────┬───────┘
                            ▼               ▼              ▼
                     DNS provider     target servers   chat / email
                        APIs          (SSH, Docker,     services
                                       panels, LBs)

        Persistent state (flat files):  ~/.acme.sh/
          account.conf · ca/<host>/<path>/{account.key,account.json,ca.conf}
          <domain>/{<domain>.key,.cer,.csr,.conf, ca.cer, fullchain.cer}
```

### 2.2 Control flow

The entire program is driven by three top-level functions:

1. **`main()`** (script entry point) — if the first argument starts with `-`, it calls `_process "$@"`; otherwise it treats the arguments as a direct internal-function call (this is how plugins and the cron wrapper re-enter the script).
2. **`_process()`** — a single large `while`/`case` loop that parses **every** CLI flag into shell variables (`_domain`, `_webroot`, `_keylength`, …), then a second `case "$_CMD"` block dispatches to the command function with the collected arguments.
3. **Command functions** — `issue`, `renew`, `renewAll`, `deploy`, `installcert`, `revoke`, `install`, `cron`, etc. These call the shared ACME-core and crypto helpers.

There are **no subcommands** in the git/docker sense; the interface is entirely flag-driven. The command verbs (`--issue`, `--renew`, …) are mutually-exclusive flags that set `_CMD`.

### 2.3 The plugin model (the key extensibility pattern)

All three plugin families follow an identical contract: a flat directory of single-file shell scripts, each `source`d on demand and invoked through a **naming convention** rather than any registration step.

| Family | Directory | Sourced when | Required function(s) | Invocation |
|---|---|---|---|---|
| DNS API | `dnsapi/` | `--dns dns_<p>` | `dns_<p>_add`, `dns_<p>_rm` | Add/remove `_acme-challenge` TXT records |
| Deploy | `deploy/` | `--deploy-hook <n>` | `<n>_deploy` | Push cert to a target |
| Notify | `notify/` | `--notify-hook <n>` | `<n>_send` | Send a renewal/error message |

When a hook runs, the core does `. "$hook_file"` to load it into the current shell, checks that the expected function exists (`_exists "$d_command"`), and calls it. A failure to find the function produces an explicit error: *"Make sure it has a function named: …"*. This convention-over-configuration design is why adding a new provider is just dropping a correctly-named file into the directory — no central manifest to edit.

Each DNS plugin also carries a self-documenting metadata block, e.g. Cloudflare's:

```sh
dns_cf_info='CloudFlare
Site: CloudFlare.com
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_cf
Options:
 CF_Key API Key
 CF_Email Your account email
OptionsAlt:
 CF_Token API Token
 CF_Account_ID Account ID
 CF_Zone_ID Zone ID. Optional.
'
```

This `dns_<p>_info` string is a machine-readable manifest of the variables each plugin reads — the authoritative source for "what credentials does provider X need."

### 2.4 The ACME protocol core

The core implements RFC 8555 directly in shell: it fetches the CA's directory resource, manages replay nonces, builds and signs JWS payloads, places new orders, answers challenges (HTTP-01, DNS-01, TLS-ALPN-01, or the draft DNS-persist-01), finalizes with a CSR, and downloads the issued chain. Renewal additionally implements **ARI (RFC 9773)** — see §7.

DNS propagation is verified before asking the CA to validate, using **DNS-over-HTTPS** so the check works regardless of the local resolver. Four DoH providers are built in (Cloudflare, Google, Alibaba, DNSPod), selectable via the `DOH` variables.

---

## 3. Cryptography

`acme.sh` performs **no cryptography itself** — every operation shells out to the `openssl` binary (overridable via `--openssl-bin` / `ACME_OPENSSL_BIN`). The script wraps openssl for:

| Operation | Helper | Notes |
|---|---|---|
| Key generation | `_createkey` | EC (`prime256v1`/`secp384r1`/`secp521r1`) via `ecparam -genkey`; RSA via `genrsa` (uses `-traditional` when available) |
| CSR creation | `_createcsr` | Builds an openssl config with SAN, extended key usage, optional OCSP-must-staple and the ACME validation extension |
| Signing (JWS) | `_sign` | RSA → `dgst -sign`; EC → DER signature parsed via `asn1parse` and re-encoded to the raw `r‖s` form ACME requires, zero-padded to the curve length |
| Digests | `_digest` | sha256/sha1/md5/sha3-256, base64 or hex output |
| HMAC | `_hmac` | For External Account Binding; falls back across two openssl invocation styles |
| Fingerprints | `_fingerprint` | Strips openssl's `=`/`:` formatting |

Because it cannot assume coreutils are present, the script reimplements **base64, hex dump, hex↔binary, and URL-encoding in pure shell** with multiple fallbacks (`xxd` → `hexdump` → `od` → an ASCII loop; `printf '\xNN'` support is probed at startup because some shells don't support it). This is the price of true portability.

> [!NOTE]
> Keys never leave the host. Account keys live under the per-CA directory; domain keys under each domain directory. `acme.sh` is a pure client — there is no phone-home telemetry and no key escrow.

---

## 4. Configuration Reference

### 4.1 Directory layout and home variables

```
~/.acme.sh/                          ← LE_WORKING_DIR (the script + plugins live here)
├── acme.sh                          ← the installed copy
├── account.conf                     ← global config (sourced as shell)
├── dnsapi/  deploy/  notify/        ← installed plugin copies
├── ca/<acme-host>/<path>/           ← per-CA account data
│   ├── account.key                  ← ACME account private key
│   ├── account.json                 ← registered account object
│   └── ca.conf                      ← per-CA config (e.g. DEFAULT_PREFERRED_CHAIN)
└── <domain>/                        ← per-certificate directory (cert NAME)
    ├── <domain>.key                 ← private key
    ├── <domain>.csr  <domain>.csr.conf
    ├── <domain>.cer                 ← leaf certificate
    ├── ca.cer                       ← intermediate
    ├── fullchain.cer                ← leaf + intermediate
    ├── <domain>.conf                ← per-domain renewal config (Le_* vars)
    └── backup/                      ← pre-renewal backups of installed files
```

ECC certificates for the same domain are stored in a parallel `<domain>_ecc/` directory (the `_ecc` suffix), so RSA and ECC certs coexist without collision.

Key location variables (set via flags or environment, persisted in `account.conf`):

| Variable | Flag | Default | Purpose |
|---|---|---|---|
| `LE_WORKING_DIR` | `--home` | `$HOME/.acme.sh` | Where the script and plugins live |
| `LE_CONFIG_HOME` | `--config-home` | `= LE_WORKING_DIR` | Where certs/config are written (separate it to keep config out of the code dir) |
| `CERT_HOME` | `--cert-home` | `= LE_CONFIG_HOME` | Root for per-domain cert directories |
| `ACCOUNT_CONF_PATH` | `--accountconf` | `<config-home>/account.conf` | Global config file |
| `CA_HOME` | — | `<config-home>/ca` | Per-CA account data root |

> [!TIP]
> For multi-tenant or packaged deployments, set `--config-home` to a writable data volume distinct from the (read-only) code directory. The Docker image and several distro packages rely on exactly this separation.

### 4.2 Configuration layering

Config is layered, each a plain shell file `source`d in order so later layers override earlier ones:

```
account.conf   (global)   →   ca/<host>/<path>/ca.conf   (per-CA)   →   <domain>/<domain>.conf   (per-cert)
```

Values are written by `_saveaccountconf` / `_savedomainconf` / `_savecaconf` and read back by the matching `_read*conf` helpers. Per-domain renewal settings are stored as `Le_*` variables (e.g. `Le_Domain`, `Le_Alt`, `Le_Webroot`, `Le_Keylength`, `Le_RenewalDays`, `Le_NextRenewTime`, `Le_API`).

Five sensitive/multiline values are **base64-encoded** in the config files to survive shell sourcing safely:

```text
Le_PreHook  Le_PostHook  Le_RenewHook  Le_Preferred_Chain  Le_ReloadCmd
```

### 4.3 Notable account-level settings

| Variable | Set via | Effect |
|---|---|---|
| `DEFAULT_ACME_SERVER` | `--set-default-ca --server <ca>` | Global default CA |
| `USER_AGENT` | `--useragent` | HTTP User-Agent (saved for reuse) |
| `ACME_OPENSSL_BIN` | `--openssl-bin` | Path to a custom openssl |
| `AUTO_UPGRADE` | `--upgrade --auto-upgrade` | Self-upgrade during cron |
| `LOG_FILE` / `LOG_LEVEL` | `--log` / `--log-level` | File logging |
| `SYS_LOG` | `--syslog <0|3|6|7>` | Syslog via `logger` |
| `NOTIFY_HOOK` / `NOTIFY_LEVEL` / `NOTIFY_MODE` / `NOTIFY_SOURCE` | `--set-notify …` | Renewal notifications |
| `ACME_USE_IPV4_REQUESTS` / `ACME_USE_IPV6_REQUESTS` | `--request-v4` / `--request-v6` | Force IP family for CA connections |
| `ACME_USE_WGET` | `--use-wget` | Force wget even if curl is present |

---

## 5. Deployment Patterns

### 5.1 Single host with a local web server (most common)

Install as the service user, issue via webroot or the Apache/Nginx mode, and install the cert with a reload command. The cron job handles everything thereafter. This is the canonical pattern in the User Guide §1.

### 5.2 Centralized issuance + push deployment

Issue certificates on a dedicated "cert host" (often using DNS API mode so no inbound HTTP is required), then use **deploy hooks** to push each renewed cert to the machines that serve it — over SSH, into Docker containers, to load balancers, or to device/panel APIs. Deploy-hook variables (`DEPLOY_<NAME>_*`) are saved per-domain on first run and replayed automatically at every renewal.

```bash
# On the cert host: issue via DNS API, then deploy to a remote node over SSH
export CF_Token=...; export CF_Account_ID=...
acme.sh --issue --dns dns_cf -d app.example.com

export DEPLOY_SSH_USER=deploy
export DEPLOY_SSH_SERVER=node1.internal
export DEPLOY_SSH_KEYFILE=/etc/ssl/app.key
export DEPLOY_SSH_FULLCHAIN_FILE=/etc/ssl/app.cer
export DEPLOY_SSH_RELOAD_CMD="systemctl reload nginx"
acme.sh --deploy -d app.example.com --deploy-hook ssh
```

### 5.3 Container deployments

The official `neilpang/acme.sh` Docker image runs the client with `~/.acme.sh` on a mounted volume. For sidecar patterns that issue on one container and serve on another, the `docker` deploy hook copies certs into a target container (matched by label) and runs a reload command inside it.

> [!WARNING]
> A recurring operational pitfall (tracked in [issue #6902](https://github.com/acmesh-official/acme.sh/issues/6902)) is that **renewal succeeds but the deploy step never re-runs**, leaving the serving container on the old cert. Deploy hooks only replay automatically if they were saved during a prior `--deploy` run. Verify after the first renewal that the target actually received the new cert (e.g. `echo | openssl s_client -connect host:443 2>/dev/null | openssl x509 -noout -dates`).

### 5.4 Stateless / DNS-persist for fleets behind proxies

When many front-ends share a domain and you cannot guarantee which one answers an HTTP challenge, use **stateless mode** (a known key authorization served by all nodes) or **DNS-persist mode** (a single long-lived TXT record reused for every renewal). Both avoid per-node challenge plumbing.

---

## 6. Scheduling & the Cron Pipeline

The installer adds a daily entry:

```text
0 0 * * * "/home/user/.acme.sh"/acme.sh --cron --home "/home/user/.acme.sh" > /dev/null
```

`--cron` sets `_ACME_IN_CRON=1`, optionally self-upgrades (if `AUTO_UPGRADE=1`), then runs `renewAll` with skip-as-success semantics. Manage the cron job independently of installation:

```bash
acme.sh --install-cronjob      # (re)install the cron entry
acme.sh --uninstall-cronjob    # remove it
```

On Windows (Cygwin), scheduling uses the Task Scheduler via `schtasks` instead of `cron`; the task is named `acme.sh.cron`.

> [!NOTE]
> Because `cron` runs with a minimal environment, `acme.sh` re-`source`s `account.conf` and exports `USER_PATH` to restore the PATH. This is why saved credentials (rather than shell-exported ones) are essential for unattended renewal.

---

## 7. Renewal Logic (ARI / RFC 9773)

A certificate is renewed when **any** condition holds:

1. `--force` is supplied.
2. The CA's **ARI `suggestedWindow`** has started.
3. The cached `Le_NextRenewTime` has passed (default fallback: `DEFAULT_RENEW=30` days).

When the CA's directory advertises a `renewalInfo` endpoint, `acme.sh` follows ARI automatically — **no opt-in**:

| Behavior | When | Why |
|---|---|---|
| Polls `suggestedWindow` | Every cron run, before deciding to skip | Lets the CA pull renewals forward during incidents (mass revocation, key compromise) |
| Picks a random time inside the window | After a successful issuance/renewal | Spreads load so clients don't stampede the CA |
| Sends `replaces=<certID>` in `newOrder` | On renewal | Correlates the new order with the cert it supersedes (RFC 9773 §5) |
| Retries without `replaces` | On `alreadyReplaced` / ARI validation error | Robustness across CA switches and retired issuers |

Inspect the computed next-renewal time:

```bash
acme.sh --info -d example.com    # Le_NextRenewTimeStr=...
```

---

## 8. Security Analysis

### 8.1 Privilege model

`acme.sh` is designed to run **unprivileged**. The working directory is created `chmod 700` and `account.conf` `chmod 600`; private keys are written with `chmod 600`. Root is required only to bind ports 80/443 (standalone), drive Apache/Nginx, or write certs into root-owned paths. A `sudo` guard (`_checkSudo`) refuses to run under a misleading `sudo` context unless `--force` is given, to prevent certs landing in the wrong home directory.

### 8.2 Secret handling

- **Credentials at rest:** DNS/deploy/notify credentials are stored **in plaintext** inside `account.conf` (or per-domain config), protected only by file permissions (`600`). Anyone who can read these files gets the API tokens. Treat the config home as sensitive; encrypt the volume or restrict the user as appropriate.
- **Secrets in logs:** sensitive values are **hidden by default** in output, debug, and log streams — they render as `[hidden](please add '--output-insecure' to see this value)` via the `_secure_debug*` helpers. `--output-insecure` (or `OUTPUT_INSECURE=1`) reveals them; use it only for short-lived debugging.

> [!CAUTION]
> Never paste `--debug 2`/`--debug 3` output that was captured with `--output-insecure` into a public issue — it can contain account keys, DNS API tokens, and EAB HMAC secrets in the clear.

### 8.3 Code-execution surface

- **Hooks are sourced shell.** DNS, deploy, and notify plugins are loaded with `.` (source) into the running shell and executed. They run with the invoking user's privileges. Only install plugin files from sources you trust; a malicious file dropped into `dnsapi/`/`deploy/`/`notify/` runs as you.
- **`eval` on user commands.** `--reloadcmd`, `--pre-hook`, `--post-hook`, and `--renew-hook` values are `eval`'d. They originate from your own config (base64-stored), so the risk is realized only if an attacker can write to your config files — another reason to lock down the config home.
- **Self-upgrade.** `--upgrade` (and auto-upgrade during cron) downloads a tarball from GitHub over HTTPS and replaces the installed script. An `UPGRADE_HASH` is recorded to skip no-op upgrades. This is convenient but means an enabled auto-upgrade trusts the upstream release channel; in high-assurance environments, pin a version and upgrade deliberately.

### 8.4 Transport security

CA and DNS API calls use HTTPS via `curl`/`wget`. `--insecure` disables peer verification (for devices with untrusted API certs) and `--ca-bundle`/`--ca-path` point at a custom trust store. Avoid `--insecure` for CA communication.

### 8.5 Hardening checklist

- [ ] Run `acme.sh` as a dedicated, unprivileged user.
- [ ] Place `--config-home` on a volume with restricted permissions; consider encryption at rest.
- [ ] Scope DNS API tokens to the single zone(s) you issue for (e.g. a Cloudflare token limited to `DNS:Edit` on one zone).
- [ ] Pin a version and upgrade deliberately in regulated environments; otherwise keep auto-upgrade on for CA-compatibility.
- [ ] Never run `--output-insecure` outside of controlled debugging; scrub any logs before sharing.
- [ ] Audit the contents of `dnsapi/`, `deploy/`, and `notify/` after any manual plugin install.
- [ ] Verify deploy actually delivered the cert after the first automated renewal.

---

## 9. Operations Runbook

| Task | Command |
|---|---|
| Health: list certs and renewal dates | `acme.sh --list` |
| Inspect one cert's config | `acme.sh --info -d example.com` |
| Force a renewal (debugging) | `acme.sh --renew -d example.com --force [--ecc]` |
| Renew everything (manual cron) | `acme.sh --renew-all` |
| Verify served cert vs issued cert | `echo \| openssl s_client -connect host:443 2>/dev/null \| openssl x509 -noout -dates -subject` |
| Reissue with a new key on renewal | add `--always-force-new-domain-key` |
| Stop renewing | `acme.sh --remove -d example.com` |
| Revoke (key compromise) | `acme.sh --revoke -d example.com --revoke-reason 1` |
| Move install | copy the whole `~/.acme.sh/`, then `--install-cronjob` |

**Backups:** the single source of truth is the config home (`~/.acme.sh/` by default). Backing up that one directory captures account keys, domain keys, certs, renewal config, and saved credentials. Restore = copy it back and reinstall the cron job.

**Logging:** enable persistent logs with `--log` (and `--log-level 2`) or route to syslog with `--syslog 6`. Without these, output goes only to stdout/stderr (and `> /dev/null` in the default cron line, so failures are silent unless you configure notifications).

> [!TIP]
> Configure **notifications** (§8 of the User Guide) on any unattended host. The default cron entry discards output, so a notify hook is your only signal that a renewal failed.

---

## 10. Repository Structure (for contributors)

```
acme.sh                 ← the entire engine (one ~8,660-line script)
dnsapi/                 ← 178 DNS provider plugins  (dns_<p>.sh)
  README.md             ← contributor notes for DNS plugins
deploy/                 ← 52 deployment hooks  (<name>.sh)
  README.md             ← contributor notes for deploy hooks
notify/                 ← 26 notification hooks  (<name>.sh)
Dockerfile              ← official image build
.github/workflows/      ← CI: ShellCheck, Pebble strict ACME tests, per-OS matrix
```

### 10.1 Authoring a plugin

To add a DNS provider, create `dnsapi/dns_<provider>.sh` exposing:

```sh
dns_<provider>_info='Provider Name
Site: provider.example
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_<provider>
Options:
 PROVIDER_Api_Key API key
'

dns_<provider>_add() {   # $1=full record name, $2=TXT value
  fulldomain="$1"; txtvalue="$2"
  # read creds (env first, saved config fallback), create the TXT record
}

dns_<provider>_rm()  {   # cleanup after validation
  fulldomain="$1"; txtvalue="$2"
}
```

Read credentials with the env-first / saved-config-fallback idiom (`${VAR:-$(_readaccountconf_mutable VAR)}`) and persist them with `_saveaccountconf_mutable` so renewals work unattended. Deploy hooks implement `<name>_deploy` and use `_getdeployconf`/`_savedeployconf`; notify hooks implement `<name>_send`.

### 10.2 Quality conventions

The codebase is checked by **ShellCheck** and validated end-to-end against the **Pebble** strict ACME test server in CI, across a large OS matrix. Plugins must be POSIX-portable (no bashisms), since the engine targets `dash`/`ash` as well as `bash`.

---

## 11. Code Quality & Known Constraints

**Strengths**

- Exceptional portability: extensive runtime feature-detection (tr variants, `sed -i` flavors, `egrep`, `printf '\x'` support, Solaris quirks) lets one codebase run nearly everywhere.
- Defense-in-depth on secrets in output (`_secure_debug` family) and conservative file permissions.
- A clean, uniform plugin contract that has scaled to **256 community plugins** without central coordination.
- Direct RFC 8555 + RFC 9773 implementation kept current with CA behavior.

**Structural constraints**

- **Monolithic engine.** The ~8,660-line single file concentrates all core logic. It is comprehensible but hard to unit-test in isolation; correctness leans heavily on the CI integration suite (ShellCheck + Pebble) rather than fine-grained tests.
- **Shell-reimplemented primitives.** base64/hex/URL-encode are hand-rolled with multiple fallbacks for portability — robust but a maintenance and audit surface that a compiled client wouldn't have.
- **Plugin maintenance load.** With 178 DNS providers, individual provider APIs drift and break; a steady stream of issues tracks per-provider breakage (e.g. CDMon, infomaniak, IP-Projects, servercow in the current issue list). Each plugin is community-maintained and only as reliable as its upstream API.

---

## 12. Improvement Opportunities

These observations come from reading the source and the current open-issue set; they are suggestions for maintainers and downstream packagers, not defects in normal operation.

1. **Deploy-after-renew reliability** ([#6902](https://github.com/acmesh-official/acme.sh/issues/6902)). Make the renew→deploy linkage more discoverable and add a post-renewal verification/notification path so a silently-skipped deploy is surfaced rather than discovered when a cert expires on the edge.

2. **Per-domain config completeness for dual RSA+ECC** ([#6986](https://github.com/acmesh-official/acme.sh/issues/6986)). `Le_DNSSleep` (and potentially other per-issuance settings) can be missing from the second key-type's `<domain>.conf` when issuing both RSA and ECC for the same names. Persist per-issuance settings symmetrically across both key directories.

3. **Validation-mode input validation.** Mistyped modes surface as generic `Unknown parameter` / `Supported validation types are…` errors ([#6962](https://github.com/acmesh-official/acme.sh/issues/6962), [#6956](https://github.com/acmesh-official/acme.sh/issues/6956)). Earlier, mode-specific validation with a hint ("did you mean `--dns dns_<x>`?") would cut a class of support issues.

4. **Secret storage option.** Plaintext credentials in `account.conf` are acceptable with strict file perms but block some compliance regimes. An optional pluggable secret backend (e.g. read tokens from a keyring/agent at runtime instead of persisting them) would broaden enterprise fit.

5. **Testability of the monolith.** Extracting pure helper groups (encoding, config I/O, CSR building) into separately `source`-able units with focused tests would reduce regression risk as the single file grows, without changing the shipped single-file ergonomics.

6. **IPv6 reach for select integrations** ([#6993](https://github.com/acmesh-official/acme.sh/issues/6993), [#6872](https://github.com/acmesh-official/acme.sh/issues/6872)). Some endpoints (e.g. a Route 53 path, ZeroSSL default-CA) are reported IPv4-only; surfacing the chosen address family in `--debug` output helps operators on IPv6-only networks diagnose quickly.

---

## 13. Quick Reference

**Commands**

| Verb | Action |
|---|---|
| `--install` / `--uninstall` / `--upgrade` | Manage the acme.sh install |
| `--issue` | Issue a certificate |
| `--renew` / `--renew-all` | Renew one / all certs |
| `--install-cert` / `-i` | Copy cert to server + set reload |
| `--deploy` | Run a deploy hook |
| `--revoke` / `--remove` | Revoke / stop tracking a cert |
| `--list` / `--info` | Inspect certs |
| `--register-account` / `--update-account` / `--deactivate-account` | Account ops |
| `--set-default-ca` / `--set-default-chain` | CA defaults |
| `--set-notify` | Configure notifications |
| `--cron` / `--install-cronjob` / `--uninstall-cronjob` | Scheduling |
| `--to-pkcs12` / `--to-pkcs8` / `--create-csr` / `--sign-csr` | Format / CSR utilities |
| `--make-dns-persist-value` | Print a DNS-persist TXT record |

**High-value parameters**

| Flag | Purpose |
|---|---|
| `-d <domain>` | Domain (repeat for SAN; first is the cert name) |
| `-w <dir>` / `--standalone` / `--alpn` / `--apache` / `--nginx` / `--dns <hook>` / `--stateless` / `--dns-persist` | Validation mode |
| `-k <len>` | Key type: `ec-256`/`ec-384`/`ec-521`/`2048`/`3072`/`4096`/`8192` |
| `--server <ca>` / `--staging` | Select CA / use staging |
| `--cert-file` / `--key-file` / `--ca-file` / `--fullchain-file` / `--reloadcmd` | Install targets |
| `--days <n>` | Renewal window (default 30) |
| `--force` | Force action |
| `--debug [0-3]` / `--log [file]` / `--output-insecure` | Diagnostics |
| `--eab-kid` / `--eab-hmac-key` | External Account Binding |
| `--home` / `--config-home` / `--cert-home` | Relocate working/config/cert dirs |

**Default file paths (per domain, under `~/.acme.sh/<domain>/`)**

`<domain>.key` · `<domain>.cer` · `ca.cer` · `fullchain.cer` · `<domain>.csr` · `<domain>.conf`

---

_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. Unofficial; not affiliated with or reviewed by the project._
