support dns-persist-01

This commit is contained in:
neil
2026-05-01 12:56:00 +02:00
committed by neil
parent 96f87844cd
commit cbb8e9068c
2 changed files with 197 additions and 26 deletions

View File

@@ -146,6 +146,7 @@
| 🌐 DNS mode | Use DNS TXT records |
| 🔗 [DNS alias mode](https://github.com/acmesh-official/acme.sh/wiki/DNS-alias-mode) | Use DNS alias for verification |
| 📡 [Stateless mode](https://github.com/acmesh-official/acme.sh/wiki/Stateless-Mode) | Stateless verification |
| 📌 DNS persist mode | Persistent DNS TXT record ([draft-ietf-acme-dns-persist-01](https://datatracker.ietf.org/doc/draft-ietf-acme-dns-persist/)) |
---
@@ -396,7 +397,50 @@ acme.sh --renew -d example.com
---
### 🔟 Issue Certificates of Different Key Types (ECC or RSA)
### 🔟 Use DNS Persist Mode
📚 Spec: [draft-ietf-acme-dns-persist-01](https://datatracker.ietf.org/doc/draft-ietf-acme-dns-persist/)
DNS persist mode lets you place a **single, longlived `_validation-persist` TXT record** in your zone and reuse it for every subsequent issuance and renewal. There is no per-issuance challenge token, so renewals require **no DNS edits** — useful when DNS API access is not available but you still want unattended renewals.
#### 🪄 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"]
```
Options:
| Flag | Description |
|------|-------------|
| `--server <ca>` | 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 <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. |
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"
```
#### ✍️ 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.
#### 📜 Step 3: Issue the certificate
```bash
acme.sh --issue -d example.com --dns-persist
```
**Done!** No challenge token is provisioned during issuance — the CA reads the persistent TXT record directly.
> 🔄 Renewals just work: `acme.sh --renew -d example.com` (or the cron job) reuses the same TXT record automatically — no further DNS edits needed.
---
### 1⃣1⃣ Issue Certificates of Different Key Types (ECC or RSA)
Just set the `keylength` to a valid, supported value.
@@ -427,7 +471,7 @@ acme.sh --issue -w /home/wwwroot/example.com -d example.com -d www.example.com -
---
### 11️⃣ Issue Wildcard Certificates
### 12️⃣ Issue Wildcard Certificates
It's simple! Just give a wildcard domain as the `-d` parameter:
@@ -439,7 +483,7 @@ acme.sh --issue -d example.com -d '*.example.com' --dns dns_cf
---
### 12️⃣ How to Renew Certificates
### 13️⃣ How to Renew Certificates
> 🔄 No need to renew manually! All certs will be renewed automatically every **30** days.
@@ -457,7 +501,7 @@ acme.sh --renew -d example.com --force --ecc
---
### 13️⃣ How to Stop Certificate Renewal
### 14️⃣ How to Stop Certificate Renewal
To stop renewal of a cert, you can execute the following to remove the cert from the renewal list:
@@ -471,7 +515,7 @@ The cert/key file is not removed from the disk.
---
### 14️⃣ How to Upgrade acme.sh
### 15️⃣ How to Upgrade acme.sh
> 🚀 acme.sh is in constant development — it's strongly recommended to use the latest code.
@@ -495,25 +539,25 @@ acme.sh --upgrade --auto-upgrade 0
---
### 15️⃣ Issue a Certificate from an Existing CSR
### 16️⃣ Issue a Certificate from an Existing CSR
📚 https://github.com/acmesh-official/acme.sh/wiki/Issue-a-cert-from-existing-CSR
---
### 16️⃣ Send Notifications in Cronjob
### 17️⃣ Send Notifications in Cronjob
📚 https://github.com/acmesh-official/acme.sh/wiki/notify
---
### 17️⃣ Under the Hood
### 18️⃣ Under the Hood
> 🔧 Speak ACME language using shell, directly to "Let's Encrypt".
---
### 18️⃣ Acknowledgments
### 19️⃣ Acknowledgments
| Project | Link |
|---------|------|
@@ -555,7 +599,7 @@ Support this project with your organization. Your logo will show up here with a
---
### 1️⃣9️⃣ License & Others
### 2️⃣0️⃣ License & Others
📄 **License:** GPLv3
@@ -565,7 +609,7 @@ Support this project with your organization. Your logo will show up here with a
---
### 20️⃣ Donate
### 21️⃣ Donate
> 💝 Your donation makes **acme.sh** better!
@@ -577,7 +621,7 @@ Support this project with your organization. Your logo will show up here with a
---
### 21️⃣ About This Repository
### 22️⃣ About This Repository
> [!NOTE]
> This repository is officially maintained by <strong>ZeroSSL</strong> as part of our commitment to providing secure and reliable SSL/TLS solutions. We welcome contributions and feedback from the community!

151
acme.sh
View File

@@ -59,6 +59,7 @@ DEFAULT_OPENSSL_BIN="openssl"
VTYPE_HTTP="http-01"
VTYPE_DNS="dns-01"
VTYPE_ALPN="tls-alpn-01"
VTYPE_DNS_PERSIST="dns-persist-01"
ID_TYPE_DNS="dns"
ID_TYPE_IP="ip"
@@ -71,6 +72,7 @@ NO_VALUE="no"
W_DNS="dns"
W_ALPN="alpn"
W_DNS_PERSIST="dns_persist"
DNS_ALIAS_PREFIX="="
MODE_STATELESS="stateless"
@@ -4028,6 +4030,85 @@ deactivateaccount() {
fi
}
#domain wildcard ca_name
#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"
if [ -z "$_mdpv_domain" ]; then
_err "Please specify a domain with -d."
return 1
fi
_initpath
_accUri="$(_readcaconf ACCOUNT_URL)"
if [ -z "$_accUri" ]; then
_info "No account is registered for $ACME_DIRECTORY yet, registering one now..."
if ! _regAccount "$DEFAULT_ACCOUNT_KEY_LENGTH"; then
_err "Cannot register account."
return 1
fi
_accUri="$(_readcaconf ACCOUNT_URL)"
fi
if [ -z "$_accUri" ]; then
_err "Cannot determine the ACME account URL."
return 1
fi
_debug "Account URL" "$_accUri"
_txt_name="_validation-persist.$_mdpv_domain"
_txt_suffix="; accounturi=$_accUri"
if [ "$_mdpv_wildcard" = "1" ]; then
_txt_suffix="$_txt_suffix; policy=wildcard"
fi
if [ -n "$_mdpv_ca_name" ]; then
_info ""
_info "Add the following DNS TXT record to enable persistent DNS validation:"
_info ""
_info "$(printf 'TXT domain: %s' "$(__green "$_txt_name")")"
_info "$(printf 'TXT value: %s' "$(__green "\"$_mdpv_ca_name$_txt_suffix\"")")"
_info ""
return 0
fi
_info "Fetching ACME directory: $ACME_DIRECTORY"
_dir_resp="$(_get "$ACME_DIRECTORY" "" 30)"
if [ "$?" != "0" ] || [ -z "$_dir_resp" ]; then
_err "Cannot fetch ACME directory: $ACME_DIRECTORY"
return 1
fi
_dir_resp="$(echo "$_dir_resp" | _json_decode)"
_debug2 _dir_resp "$_dir_resp"
_caa_array="$(echo "$_dir_resp" | tr -d ' \r\n\t' | _egrep_o '"caaIdentities":\[[^]]*\]')"
_debug2 _caa_array "$_caa_array"
_caaids="$(echo "$_caa_array" | sed 's/.*\[//' | sed 's/\].*//' | tr ',' '\n' | tr -d '"')"
_debug2 _caaids "$_caaids"
if [ -z "$_caaids" ]; then
_err "The directory does not include 'caaIdentities'. Please specify --dns-persist-ca-name explicitly."
return 1
fi
_info ""
_info "Add ANY ONE of the following DNS TXT records to enable persistent DNS validation."
_info "(You only need to add one; pick whichever issuer identity you prefer.)"
for _id in $_caaids; do
[ -z "$_id" ] && continue
_info ""
_info "$(printf 'TXT domain: %s' "$(__green "$_txt_name")")"
_info "$(printf 'TXT value : %s' "$(__green "\"$_id$_txt_suffix\"")")"
done
_info ""
}
# domain folder file
_findHook() {
_hookdomain="$1"
@@ -4806,7 +4887,9 @@ $_authorizations_map"
vtype="$VTYPE_HTTP"
#todo, v2 wildcard force to use dns
if _startswith "$_currentRoot" "$W_DNS"; then
if [ "$_currentRoot" = "$W_DNS_PERSIST" ]; then
vtype="$VTYPE_DNS_PERSIST"
elif _startswith "$_currentRoot" "$W_DNS"; then
vtype="$VTYPE_DNS"
fi
@@ -4864,6 +4947,23 @@ $_authorizations_map"
fi
if [ -z "$keyauthorization" ]; then
uri="$(echo "$entry" | _egrep_o '"url":"[^"]*' | cut -d '"' -f 4 | _head_n 1)"
_debug uri "$uri"
if [ -z "$uri" ]; then
_err "Cannot get domain URI $entry"
_clearup
_on_issue_err "$_post_hook"
return 1
fi
if [ "$vtype" = "$VTYPE_DNS_PERSIST" ]; then
# dns-persist-01 challenges have no token; the TXT record is
# provisioned out-of-band. Use a non-empty placeholder so the
# downstream code does not treat this entry as already verified.
keyauthorization="$VTYPE_DNS_PERSIST"
_debug keyauthorization "$keyauthorization"
else
token="$(echo "$entry" | _egrep_o '"token":"[^"]*' | cut -d : -f 2 | tr -d '"')"
_debug token "$token"
@@ -4873,20 +4973,10 @@ $_authorizations_map"
_on_issue_err "$_post_hook"
return 1
fi
uri="$(echo "$entry" | _egrep_o '"url":"[^"]*' | cut -d '"' -f 4 | _head_n 1)"
_debug uri "$uri"
if [ -z "$uri" ]; then
_err "Cannot get domain URI $entry"
_clearup
_on_issue_err "$_post_hook"
return 1
fi
keyauthorization="$token.$thumbprint"
_debug keyauthorization "$keyauthorization"
fi
fi
dvlist="$d$sep$keyauthorization$sep$uri$sep$vtype$sep$_currentRoot$sep$_authz_url"
_debug dvlist "$dvlist"
@@ -7158,6 +7248,8 @@ Commands:
--update-account Update account info.
--register-account Register account key.
--deactivate-account Deactivate the account.
--make-dns-persist-value Print the DNS TXT record(s) to enable persistent DNS validation
(draft-ietf-acme-dns-persist-01). Use with -d <domain>.
--create-account-key Create an account private key, professional use.
--install-cronjob Install the cron job to renew certs, you don't need to call this. The 'install' command can automatically install the cron job.
--uninstall-cronjob Uninstall the cron job. The 'uninstall' command can do this automatically.
@@ -7205,6 +7297,10 @@ Parameters:
--dns [dns_hook] Use dns manual mode or dns api. Defaults to manual mode when argument is omitted.
See: $_DNS_API_WIKI
--dns-persist Use dns-persist-01 validation (draft-ietf-acme-dns-persist-01).
Requires the persistent _validation-persist TXT record to already
exist. Use '--make-dns-persist-value' to print the value to add.
--dnssleep <seconds> The time in seconds to wait for all the txt records to propagate in dns api mode.
It's not necessary to use this by default, $PROJECT_NAME polls dns status by DOH automatically.
-k, --keylength <bits> Specifies the domain key length: 2048, 3072, 4096, 8192 or ec-256, ec-384, ec-521.
@@ -7215,6 +7311,14 @@ Parameters:
--eab-kid <eab_key_id> Key Identifier for External Account Binding.
--eab-hmac-key <eab_hmac_key> HMAC key for External Account Binding.
--dns-persist-wildcard Used with '--make-dns-persist-value'. Adds 'policy=wildcard' to the
generated TXT record so the issuer is also authorized for wildcards
and subdomains (draft-ietf-acme-dns-persist-01).
--dns-persist-ca-name <name> Used with '--make-dns-persist-value'. Use the given CA identity domain
(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.
These parameters are to install the cert to nginx/Apache or any other server after issue/renew a cert:
@@ -7585,6 +7689,8 @@ _process() {
_valid_to=""
_certificate_profile=""
_extended_key_usage=""
_dns_persist_wildcard=""
_dns_persist_ca_name=""
while [ ${#} -gt 0 ]; do
case "${1}" in
@@ -7679,6 +7785,16 @@ _process() {
--deactivate-account)
_CMD="deactivateaccount"
;;
--make-dns-persist-value | --makednspersistvalue)
_CMD="makednspersistvalue"
;;
--dns-persist-wildcard | --dnspersistwildcard)
_dns_persist_wildcard="1"
;;
--dns-persist-ca-name | --dnspersistcaname)
_dns_persist_ca_name="$2"
shift
;;
--set-notify)
_CMD="setnotify"
;;
@@ -7822,6 +7938,14 @@ _process() {
_webroot="$_webroot,$wvalue"
fi
;;
--dns-persist)
wvalue="$W_DNS_PERSIST"
if [ -z "$_webroot" ]; then
_webroot="$wvalue"
else
_webroot="$_webroot,$wvalue"
fi
;;
--dnssleep)
_dnssleep="$2"
Le_DNSSleep="$_dnssleep"
@@ -8238,6 +8362,9 @@ _process() {
deactivateaccount)
deactivateaccount
;;
makednspersistvalue)
makednspersistvalue "$_domain" "$_dns_persist_wildcard" "$_dns_persist_ca_name"
;;
list)
list "$_listraw" "$_domain"
;;