Jesse A. Green / notebook

Live demo: the DocCompiler guide library as a static, read-only snapshot (October 2026). No backend, no accounts, nothing to submit.

acme.sh Administrator & Architecture 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.

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, 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) 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 sourced 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:

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 sourced 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:

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
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.

# 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) 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:

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:

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-sources 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:

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:

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). 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). 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, #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, #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