Version
v3.1.2 confirmed reproducing; v3.1.4 still contains the same code paths.
Environment
- macOS 15.5 (Darwin 25.5.0), bash
- CA: ZeroSSL (acme.zerossl.com/v2/DV90)
- Cert type: ECC wildcard (
*.example.com)
- Renewal mode: manual DNS (
--dns --yes-I-know-dns-manual-mode-enough-go-ahead-please)
Reproduction
- Issue a wildcard cert in manual DNS mode.
Le_LinkOrder and Le_LinkCert get written to the domain's .conf.
- ~90 days later, run
--renew ... --dns --yes-I-know-dns-manual-mode-enough-go-ahead-please.
- First invocation prints the TXT challenge and exits (expected).
- Add the TXT record; second invocation reports "Cert success" and writes the cert files.
Actual result: the written cert is byte-for-byte identical to the previous cert (same serial, fingerprint, notAfter — still expired). acme.sh only reissues on the very first --issue; every subsequent manual-DNS --renew silently re-downloads the previous cert.
Log excerpt showing the bug
```
Le_OrderFinalize='https://acme.example/v2/.../order/NEW-ORDER-ID/finalize'
Order status is 'processing', let's sleep and retry.
Polling order status: https://acme.example/v2/.../order/OLD-ORDER-ID <-- previous run's order
Le_LinkCert='https://acme.example/v2/.../cert/OLD-CERT-ID' <-- previous run's cert
```
Root cause
In `_issue()`:
acme.sh:4862-4866 (v3.1.4) — the reset is intentionally skipped for DNS manual mode (to preserve state across the two-invocation flow), but this preserves state from the previous cert's lifetime, not just the current run:
```sh
elif ! _hasfield "$_web_roots" "$W_DNS"; then
Le_OrderFinalize=""
Le_LinkOrder=""
Le_LinkCert=""
fi
```
acme.sh:5689-5691 (v3.1.4) — after finalizing a fresh order, `Le_LinkOrder` is only pulled from response headers when the variable is empty. On renewal it isn't, so the subsequent poll (`_info "Polling order status: $Le_LinkOrder"`) hits the old order, which is still `status:valid` and returns the old certificate URL:
```sh
if [ -z "$Le_LinkOrder" ]; then
Le_LinkOrder="$(echo "$responseHeaders" | grep -i '^Location.*$' | ...)"
fi
```
Workaround
Manually strip the three variables from the domain `.conf` before running `--renew`:
```sh
sed -i '' "/^Le_LinkOrder=/d;/^Le_LinkCert=/d;/^Le_OrderFinalize=/d" ~/.acme.sh/DOMAIN_ecc/DOMAIN.conf
```
Then `--issue --force` produces a genuinely new cert.
Suggested fixes (any one)
- Overwrite unconditionally after finalize: drop the `[ -z "$Le_LinkOrder" ]` guard and always pull from response headers.
- Reset `Le_LinkOrder`/`Le_LinkCert` at the top of the finalize step (not the top of `_issue`), so the manual-DNS state preservation doesn't include them.
- Reset these three variables whenever a new order is created in this run.
Happy to submit a PR if a preferred approach is confirmed.
Version
v3.1.2 confirmed reproducing; v3.1.4 still contains the same code paths.
Environment
*.example.com)--dns --yes-I-know-dns-manual-mode-enough-go-ahead-please)Reproduction
Le_LinkOrderandLe_LinkCertget written to the domain's.conf.--renew ... --dns --yes-I-know-dns-manual-mode-enough-go-ahead-please.Actual result: the written cert is byte-for-byte identical to the previous cert (same serial, fingerprint, notAfter — still expired). acme.sh only reissues on the very first
--issue; every subsequent manual-DNS--renewsilently re-downloads the previous cert.Log excerpt showing the bug
```
Le_OrderFinalize='https://acme.example/v2/.../order/NEW-ORDER-ID/finalize'
Order status is 'processing', let's sleep and retry.
Polling order status: https://acme.example/v2/.../order/OLD-ORDER-ID <-- previous run's order
Le_LinkCert='https://acme.example/v2/.../cert/OLD-CERT-ID' <-- previous run's cert
```
Root cause
In `_issue()`:
acme.sh:4862-4866 (v3.1.4) — the reset is intentionally skipped for DNS manual mode (to preserve state across the two-invocation flow), but this preserves state from the previous cert's lifetime, not just the current run:
```sh
elif ! _hasfield "$_web_roots" "$W_DNS"; then
Le_OrderFinalize=""
Le_LinkOrder=""
Le_LinkCert=""
fi
```
acme.sh:5689-5691 (v3.1.4) — after finalizing a fresh order, `Le_LinkOrder` is only pulled from response headers when the variable is empty. On renewal it isn't, so the subsequent poll (`_info "Polling order status: $Le_LinkOrder"`) hits the old order, which is still `status:valid` and returns the old certificate URL:
```sh
if [ -z "$Le_LinkOrder" ]; then
Le_LinkOrder="$(echo "$responseHeaders" | grep -i '^Location.*$' | ...)"
fi
```
Workaround
Manually strip the three variables from the domain `.conf` before running `--renew`:
```sh
sed -i '' "/^Le_LinkOrder=/d;/^Le_LinkCert=/d;/^Le_OrderFinalize=/d" ~/.acme.sh/DOMAIN_ecc/DOMAIN.conf
```
Then `--issue --force` produces a genuinely new cert.
Suggested fixes (any one)
Happy to submit a PR if a preferred approach is confirmed.