diff --git a/README.md b/README.md index b2f22f3f..b87eeedd 100644 --- a/README.md +++ b/README.md @@ -406,7 +406,7 @@ DNS persist mode lets you place a **single, long‑lived `_validation-persist` T #### πŸͺ„ Step 1: Print the TXT record value ```bash -acme.sh --make-dns-persist-value -d example.com [--server letsencrypt] [--dns-persist-wildcard] [--dns-persist-ca-name "sectigo.com"] +acme.sh --make-dns-persist-value -d example.com [--server letsencrypt] [--dns-persist-wildcard] [--dns-persist-ca-name "sectigo.com"] [--dns-persist-days 365] ``` Options: @@ -416,17 +416,18 @@ Options: | `--server ` | Pick the CA (default is your configured default). The account is registered automatically if you have not used this CA before. | | `--dns-persist-wildcard` | Adds `policy=wildcard` to the record so it also authorizes wildcard / subdomain certs. | | `--dns-persist-ca-name ` | Use a specific CA identity domain (e.g. `sectigo.com`). If omitted, identities are read from the ACME directory's `caaIdentities` field and one record per identity is printed β€” you only need to add **any one** of them. | +| `--dns-persist-days ` | Adds `persistUntil=` to the record, set to N days from now. The CA will refuse new validations against the record after that time. Omit for a record with no expiry. | You should get an output like: ```sh -TXT domain: _validation-persist.example.com -TXT value: "letsencrypt.org; accounturi=https://acme-v02.api.letsencrypt.org/acme/acct/123456789" +TXT persist domain:_validation-persist.example.com +TXT persist value :"letsencrypt.org; accounturi=https://acme-v02.api.letsencrypt.org/acme/acct/123456789" ``` #### ✍️ Step 2: Add the TXT record to your DNS -Add the printed `TXT domain` / `TXT value` pair as a TXT record at your DNS provider, then wait for it to propagate. +Add the printed `TXT persist domain` / `TXT persist value` pair as a TXT record at your DNS provider, then wait for it to propagate. #### πŸ“œ Step 3: Issue the certificate @@ -485,7 +486,7 @@ acme.sh --issue -d example.com -d '*.example.com' --dns dns_cf ### 1️⃣3️⃣ How to Renew Certificates -> πŸ”„ No need to renew manually! All certs will be renewed automatically every **30** days. +> πŸ”„ No need to renew manually! All certs will be renewed automatically every **30** days, **or earlier when the CA's ARI says so** (see below). However, you can force a renewal: @@ -499,6 +500,38 @@ acme.sh --renew -d example.com --force acme.sh --renew -d example.com --force --ecc ``` +#### πŸ“‘ ACME Renewal Information (ARI) β€” RFC 9773 + +If the CA exposes a `renewalInfo` endpoint in its ACME directory (Let's Encrypt, ZeroSSL, etc.), `acme.sh` follows [RFC 9773](https://www.rfc-editor.org/rfc/rfc9773.html) automatically β€” **no flag needed, no opt-in**: + +| What | When | Why | +|------|------|-----| +| πŸ” **Polls `suggestedWindow`** | Every cron run, before deciding to skip | Lets the CA shift the renewal time forward in case of an incident (key compromise, mass revocation, etc.) | +| 🎯 **Picks a random renewal time** inside the window | Right after a successful issuance/renewal | Disperses renewals across the network so all clients don't hit the CA at the same instant | +| πŸ”— **Sends `replaces=`** in `newOrder` | On renewal | Lets the CA correlate the new order with the certificate it supersedes (RFC 9773 Β§5) | +| ↩️ **Retries without `replaces`** | If the CA rejects with `alreadyReplaced` or an ARI validation error | Robust against edge cases (e.g. switching CAs, retired issuers) | + +**Renewal trigger logic:** the cert is renewed if **any one** of the following becomes true: + +1. `--force` is given +2. The CA's **ARI `suggestedWindow` has started** +3. The cached `Le_NextRenewTime` has passed (default fallback for CAs without ARI) + +You can see the resulting next renewal time (already ARI-picked when applicable) in: + +```sh +acme.sh --info -d example.com +# Look for: Le_NextRenewTimeStr=... +``` + +For the live ARI window the CA is currently advertising, run with `--debug 2`: + +```sh +acme.sh --renew -d example.com --debug 2 2>&1 | grep -i 'ARI suggestedWindow' +``` + +> πŸ’‘ If your CA does not advertise `renewalInfo`, `acme.sh` falls back to the classic 30-day rule β€” no behavior change. + --- ### 1️⃣4️⃣ How to Stop Certificate Renewal diff --git a/acme.sh b/acme.sh index 9e37a406..1dabd2a3 100755 --- a/acme.sh +++ b/acme.sh @@ -4030,19 +4030,33 @@ deactivateaccount() { fi } -#domain wildcard ca_name +#domain wildcard ca_name days #Print the TXT record(s) the user must add to enable persistent DNS validation #per draft-ietf-acme-dns-persist-01. makednspersistvalue() { _mdpv_domain="$1" _mdpv_wildcard="$2" _mdpv_ca_name="$3" + _mdpv_days="$4" if [ -z "$_mdpv_domain" ]; then _err "Please specify a domain with -d." return 1 fi + if [ -n "$_mdpv_days" ]; then + case "$_mdpv_days" in + '' | *[!0-9]*) + _err "--dns-persist-days must be a positive integer, got: $_mdpv_days" + return 1 + ;; + esac + if [ "$_mdpv_days" -lt 1 ]; then + _err "--dns-persist-days must be at least 1." + return 1 + fi + fi + _initpath _accUri="$(_readcaconf ACCOUNT_URL)" @@ -4067,6 +4081,11 @@ makednspersistvalue() { if [ "$_mdpv_wildcard" = "1" ]; then _txt_suffix="$_txt_suffix; policy=wildcard" fi + if [ -n "$_mdpv_days" ]; then + _persist_until=$(_math "$(_time)" + "$_mdpv_days" \* 86400) + _txt_suffix="$_txt_suffix; persistUntil=$_persist_until" + _info "persistUntil set to $(__green "$(_time2str "$_persist_until")") ($_mdpv_days days from now)" + fi if [ -n "$_mdpv_ca_name" ]; then _info "" @@ -7402,6 +7421,10 @@ Parameters: (e.g. 'ssl.com') as the issuer-domain-name in the TXT record. If omitted, the identities are read from the ACME directory's 'caaIdentities' field and one record is printed per identity. + --dns-persist-days Used with '--make-dns-persist-value'. Add a 'persistUntil' field to + the TXT record so the record self-expires N days from now (the CA + will refuse new validations against the record after that time). + If omitted, the record has no expiry. These parameters are to install the cert to nginx/Apache or any other server after issue/renew a cert: @@ -7775,6 +7798,7 @@ _process() { _extended_key_usage="" _dns_persist_wildcard="" _dns_persist_ca_name="" + _dns_persist_days="" while [ ${#} -gt 0 ]; do case "${1}" in @@ -7879,6 +7903,10 @@ _process() { _dns_persist_ca_name="$2" shift ;; + --dns-persist-days | --dnspersistdays) + _dns_persist_days="$2" + shift + ;; --set-notify) _CMD="setnotify" ;; @@ -8447,7 +8475,7 @@ _process() { deactivateaccount ;; makednspersistvalue) - makednspersistvalue "$_domain" "$_dns_persist_wildcard" "$_dns_persist_ca_name" + makednspersistvalue "$_domain" "$_dns_persist_wildcard" "$_dns_persist_ca_name" "$_dns_persist_days" ;; list) list "$_listraw" "$_domain"