# verify configuration values
verify_config() {
- [[ "${CHALLENGETYPE}" == "http-01" || "${CHALLENGETYPE}" == "dns-01" || "${CHALLENGETYPE}" == "tls-alpn-01" ]] || _exiterr "Unknown challenge type ${CHALLENGETYPE}... cannot continue."
+ [[ "${CHALLENGETYPE}" == "http-01" || "${CHALLENGETYPE}" == "dns-01" || "${CHALLENGETYPE}" == "dns-persist-01" || "${CHALLENGETYPE}" == "tls-alpn-01" ]] || _exiterr "Unknown challenge type ${CHALLENGETYPE}... cannot continue."
if [[ "${COMMAND:-}" =~ sign_domains|sign_csr ]]; then
if [[ "${CHALLENGETYPE}" = "dns-01" ]] && [[ -z "${HOOK}" ]]; then
_exiterr "Challenge type dns-01 needs a hook script for deployment... cannot continue."
# Generate DNS entry content for dns-01 validation
keyauth_hook="$(printf '%s' "${keyauth}" | "${OPENSSL}" dgst -sha256 -binary | urlbase64)"
;;
+ "dns-persist-01")
+ # Pre-existing persistent DNS record is expected; no deploy/cleanup by dehydrated.
+ keyauth_hook=""
+ ;;
"tls-alpn-01")
keyauth_hook="$(printf '%s' "${keyauth}" | "${OPENSSL}" dgst -sha256 -c -hex | awk '{print $NF}')"
generate_alpn_certificate "${identifier}" "${identifier_type}" "${keyauth_hook}"
# Deploy challenge tokens
if [[ ${num_pending_challenges} -ne 0 ]]; then
- echo " + Deploying challenge tokens..."
- if [[ -n "${HOOK}" ]] && [[ "${HOOK_CHAIN}" = "yes" ]]; then
- # shellcheck disable=SC2068
- "${HOOK}" "deploy_challenge" ${deploy_args[@]} || _exiterr 'deploy_challenge hook returned with non-zero exit code'
- elif [[ -n "${HOOK}" ]]; then
- # Run hook script to deploy the challenge token
- local idx=0
- while [ ${idx} -lt ${num_pending_challenges} ]; do
- # shellcheck disable=SC2086
- "${HOOK}" "deploy_challenge" ${deploy_args[${idx}]} || _exiterr 'deploy_challenge hook returned with non-zero exit code'
- idx=$((idx+1))
- done
+ if [[ "${CHALLENGETYPE}" != "dns-persist-01" ]]; then
+ echo " + Deploying challenge tokens..."
+ if [[ -n "${HOOK}" ]] && [[ "${HOOK_CHAIN}" = "yes" ]]; then
+ # shellcheck disable=SC2068
+ "${HOOK}" "deploy_challenge" ${deploy_args[@]} || _exiterr 'deploy_challenge hook returned with non-zero exit code'
+ elif [[ -n "${HOOK}" ]]; then
+ # Run hook script to deploy the challenge token
+ local idx=0
+ while [ ${idx} -lt ${num_pending_challenges} ]; do
+ # shellcheck disable=SC2086
+ "${HOOK}" "deploy_challenge" ${deploy_args[${idx}]} || _exiterr 'deploy_challenge hook returned with non-zero exit code'
+ idx=$((idx+1))
+ done
+ fi
fi
fi
done
if [[ ${num_pending_challenges} -ne 0 ]]; then
- echo " + Cleaning challenge tokens..."
+ if [[ "${CHALLENGETYPE}" != "dns-persist-01" ]]; then
+ echo " + Cleaning challenge tokens..."
- # Clean challenge tokens using chained hook
- # shellcheck disable=SC2068
- [[ -n "${HOOK}" ]] && [[ "${HOOK_CHAIN}" = "yes" ]] && ("${HOOK}" "clean_challenge" ${deploy_args[@]} || _exiterr 'clean_challenge hook returned with non-zero exit code')
+ # Clean challenge tokens using chained hook
+ # shellcheck disable=SC2068
+ [[ -n "${HOOK}" ]] && [[ "${HOOK_CHAIN}" = "yes" ]] && ("${HOOK}" "clean_challenge" ${deploy_args[@]} || _exiterr 'clean_challenge hook returned with non-zero exit code')
- # Clean remaining challenge tokens if validation has failed
- local idx=0
- while [ ${idx} -lt ${num_pending_challenges} ]; do
- # Delete challenge file
- [[ "${CHALLENGETYPE}" = "http-01" ]] && rm -f "${WELLKNOWN}/${challenge_tokens[${idx}]}"
- # Delete alpn verification certificates
- [[ "${CHALLENGETYPE}" = "tls-alpn-01" ]] && rm -f "${ALPNCERTDIR}/${challenge_names[${idx}]}.crt.pem" "${ALPNCERTDIR}/${challenge_names[${idx}]}.key.pem"
- # Clean challenge token using non-chained hook
- # shellcheck disable=SC2086
- [[ -n "${HOOK}" ]] && [[ "${HOOK_CHAIN}" != "yes" ]] && ("${HOOK}" "clean_challenge" ${deploy_args[${idx}]} || _exiterr 'clean_challenge hook returned with non-zero exit code')
- idx=$((idx+1))
- done
+ # Clean remaining challenge tokens if validation has failed
+ local idx=0
+ while [ ${idx} -lt ${num_pending_challenges} ]; do
+ # Delete challenge file
+ [[ "${CHALLENGETYPE}" = "http-01" ]] && rm -f "${WELLKNOWN}/${challenge_tokens[${idx}]}"
+ # Delete alpn verification certificates
+ [[ "${CHALLENGETYPE}" = "tls-alpn-01" ]] && rm -f "${ALPNCERTDIR}/${challenge_names[${idx}]}.crt.pem" "${ALPNCERTDIR}/${challenge_names[${idx}]}.key.pem"
+ # Clean challenge token using non-chained hook
+ # shellcheck disable=SC2086
+ [[ -n "${HOOK}" ]] && [[ "${HOOK_CHAIN}" != "yes" ]] && ("${HOOK}" "clean_challenge" ${deploy_args[${idx}]} || _exiterr 'clean_challenge hook returned with non-zero exit code')
+ idx=$((idx+1))
+ done
+ fi
if [[ "${reqstatus}" != "valid" ]]; then
echo " + Challenge validation has failed :("
PARAM_ALPNCERTDIR="${1}"
;;
- # PARAM_Usage: --challenge (-t) http-01|dns-01|tls-alpn-01
- # PARAM_Description: Which challenge should be used? Currently http-01, dns-01, and tls-alpn-01 are supported
+ # PARAM_Usage: --challenge (-t) http-01|dns-01|dns-persist-01|tls-alpn-01
+ # PARAM_Description: Which challenge should be used? Currently http-01, dns-01, dns-persist-01 and tls-alpn-01 are supported
--challenge|-t)
shift 1
check_parameters "${1:-}"
You can delete the TXT record when called with operation `clean_challenge`, when $2 is also the domain name.
Here are some examples: [Examples for DNS-01 hooks](https://github.com/dehydrated-io/dehydrated/wiki)
+
+### dns-persist-01 challenge
+
+This script also supports the `dns-persist-01`-type verification. This type of verification requires you to create a persistent `TXT` DNS record containing your Let's Encrypt account information.
+
+Unlike `dns-01`, which requires dynamic DNS record updates for each certificate request, `dns-persist-01` uses a single persistent record that remains in place indefinitely.
+
+You need to create a TXT record named `_validation-persist` in the domain for which you want to request certificates. The record should contain your account URI and other metadata.
+
+Example record:
+```
+_validation-persist.example.com. IN TXT (
+ "letsencrypt.org;"
+ " accounturi=https://acme-v02.api.letsencrypt.org/acme/acct/1234567890;"
+ " policy=wildcard"
+)
+```
+
+The account URI can be obtained by running `dehydrated --register --accept-terms` and checking the account registration response, or by examining the `accounts/*/registration.json` file after registration.
+
+This record should be set up once and left in place. No hook script is required for `dns-persist-01` as dehydrated does not perform any dynamic DNS updates for this challenge type.
Alternative Name\fR (SAN) field.
For the next step, one way of verifying domain name ownership needs to be
-configured. Dehydrated implements \fIhttp-01\fR and \fIdns-01\fR verification.
+configured. Dehydrated implements \fIhttp-01\fR, \fIdns-01\fR, and \fIdns-persist-01\fR verification.
The \fIhttp-01\fR verification provides proof of ownership by providing a
challenge token. In order to do that, the directory referenced in the
available for dehydrated. See \fIdns-verification.md\fR for hooks for popular
DNS servers and DNS hosters.
+The \fIdns-persist-01\fR verification works by providing a persistent DNS record
+containing account information. Unlike \fIdns-01\fR, this requires setting up a
+static TXT record once that remains in place indefinitely. No dynamic DNS
+updates are performed during certificate requests. See \fIdns-verification.md\fR
+for details on setting up the required DNS record.
+
Finally, the certificates need to be requested and updated on a regular basis.
This can happen through a cron job or a timer. Initially, you may enforce this
by invoking \fIdehydrated -c\fR manually.