Error: Challenge is invalid #457

Closed
opened 2025-12-29 01:25:36 +01:00 by adam · 6 comments
Owner

Originally created by @ronaldtveen on GitHub (Feb 4, 2020).

Any idea what's causing this error?

I'm trying to setup DNS-01 validation (wildcard, in the future) for my domain. Wanted to try and use Dehydrated in combination with Lexicon (Python DNS management script), which is described here:
https://www.aaflalo.me/2017/02/lets-encrypt-with-dehydrated-dns-01/

When I run it though, I get an error that the challenge is invalid. I masked my personal domain to example.com.

pi@raspi:/etc/dehydrated $ sudo /home/pi/dehydrated/dehydrated -c -f /etc/dehydrated/config
# INFO: Using main config file /etc/dehydrated/config
Processing sub.example.com
 + Signing domains...
 + Generating private key...
 + Generating signing request...
 + Requesting new certificate order from CA...
 + Received 1 authorizations URLs from the CA
 + Handling authorization for sub.example.com
 + 1 pending challenge(s)
 + Deploying challenge tokens...
RESULT
------
True
 + Responding to challenge for sub.example.com authorization...
 + Cleaning challenge tokens...
 + Challenge validation has failed :(
ERROR: Challenge is invalid! (returned: invalid) (result: {
  "type": "dns-01",
  "status": "invalid",
  "error": {
    "type": "urn:ietf:params:acme:error:dns",
    "detail": "DNS problem: NXDOMAIN looking up TXT for _acme-challenge.sub.example.com - check that a DNS record exists for this domain",
    "status": 400
  },
  "url": "https://acme-v02.api.letsencrypt.org/acme/chall-v3/1234567890/a1bcdE",
  "token": "WMtOKoeOrFpf-XXXXXXXXXXDJcbalf6wUKt2kp7tM4"
})

But when I look in my DNS records when the script is running I see this popping up:

_acme-challenge.sub      IN    TXT    0RfcthTmzfX_1I6aZ1hXXXXXXXXXXXHGa67qVyHdY

This token is completely different from the token that Dehydrated shown me in the error in the end.
Right after the script the TXT record has been deleted by lexicon.

My config

pi@raspi:~/dehydrated $ cat /etc/dehydrated/config 
CHALLENGETYPE="dns-01"

HOOK="${BASEDIR}/hook.sh"

CONTACT_EMAIL="my@emailaddress.com"

My hooks.sh

pi@raspi:~/dehydrated $ cat /etc/dehydrated/hook.sh 
#!/usr/bin/env bash

# setup provider environmental variables:
export PROVIDER=TRANSIP
export LEXICON_TRANSIP_USERNAME="myusername"
export LEXICON_TRANSIP_API_KEY="/etc/dehydrated/secret.key"

tld_domain() {
    local DOMAIN="${1}" 
    
    echo $(echo "$DOMAIN" | awk -F'.' '{gsub("http://|/.*","")} NF>2{$1="";$0=substr($0, 2)}1' OFS='.')
}

deploy_challenge() {
    local DOMAIN="${1}" TOKEN_FILENAME="${2}" TOKEN_VALUE="${3}"

    TLD_DOMAIN=$(tld_domain $DOMAIN)
    
    lexicon transip create ${TLD_DOMAIN} TXT --name="_acme-challenge.${DOMAIN}." --content="$TOKEN_VALUE"  
}

clean_challenge() {
    local DOMAIN="${1}" TOKEN_FILENAME="${2}" TOKEN_VALUE="${3}"
 
    TLD_DOMAIN=$(tld_domain $DOMAIN)
    
    #lexicon transip delete ${TLD_DOMAIN} TXT --name="_acme-challenge.${DOMAIN}."
}

deploy_cert() {
    local DOMAIN="${1}" KEYFILE="${2}" CERTFILE="${3}" FULLCHAINFILE="${4}" CHAINFILE="${5}" TIMESTAMP="${6}"

    systemctl restart home-assistant@homeassistant
    systemctl restart apache2.service
}

unchanged_cert() {
    local DOMAIN="${1}" KEYFILE="${2}" CERTFILE="${3}" FULLCHAINFILE="${4}" CHAINFILE="${5}"
}

invalid_challenge() {
    local DOMAIN="${1}" RESPONSE="${2}"
}

request_failure() {
    local STATUSCODE="${1}" REASON="${2}" REQTYPE="${3}"
}

exit_hook() {
  :
}

HANDLER="$1"; shift
if [[ "${HANDLER}" =~ ^(deploy_challenge|clean_challenge|deploy_cert|unchanged_cert|invalid_challenge|request_failure|exit_hook)$ ]]; then
  "$HANDLER" "$@"
fi

Any idea what's going wrong here?

Originally created by @ronaldtveen on GitHub (Feb 4, 2020). Any idea what's causing this error? I'm trying to setup DNS-01 validation (wildcard, in the future) for my domain. Wanted to try and use Dehydrated in combination with Lexicon (Python DNS management script), which is described here: https://www.aaflalo.me/2017/02/lets-encrypt-with-dehydrated-dns-01/ When I run it though, I get an error that the challenge is invalid. I masked my personal domain to example.com. ``` pi@raspi:/etc/dehydrated $ sudo /home/pi/dehydrated/dehydrated -c -f /etc/dehydrated/config # INFO: Using main config file /etc/dehydrated/config Processing sub.example.com + Signing domains... + Generating private key... + Generating signing request... + Requesting new certificate order from CA... + Received 1 authorizations URLs from the CA + Handling authorization for sub.example.com + 1 pending challenge(s) + Deploying challenge tokens... RESULT ------ True + Responding to challenge for sub.example.com authorization... + Cleaning challenge tokens... + Challenge validation has failed :( ERROR: Challenge is invalid! (returned: invalid) (result: { "type": "dns-01", "status": "invalid", "error": { "type": "urn:ietf:params:acme:error:dns", "detail": "DNS problem: NXDOMAIN looking up TXT for _acme-challenge.sub.example.com - check that a DNS record exists for this domain", "status": 400 }, "url": "https://acme-v02.api.letsencrypt.org/acme/chall-v3/1234567890/a1bcdE", "token": "WMtOKoeOrFpf-XXXXXXXXXXDJcbalf6wUKt2kp7tM4" }) ``` But when I look in my DNS records when the script is running I see this popping up: ``` _acme-challenge.sub IN TXT 0RfcthTmzfX_1I6aZ1hXXXXXXXXXXXHGa67qVyHdY ``` This token is completely different from the token that Dehydrated shown me in the error in the end. Right after the script the TXT record has been deleted by lexicon. My `config` ``` pi@raspi:~/dehydrated $ cat /etc/dehydrated/config CHALLENGETYPE="dns-01" HOOK="${BASEDIR}/hook.sh" CONTACT_EMAIL="my@emailaddress.com" ``` My `hooks.sh` ``` pi@raspi:~/dehydrated $ cat /etc/dehydrated/hook.sh #!/usr/bin/env bash # setup provider environmental variables: export PROVIDER=TRANSIP export LEXICON_TRANSIP_USERNAME="myusername" export LEXICON_TRANSIP_API_KEY="/etc/dehydrated/secret.key" tld_domain() { local DOMAIN="${1}" echo $(echo "$DOMAIN" | awk -F'.' '{gsub("http://|/.*","")} NF>2{$1="";$0=substr($0, 2)}1' OFS='.') } deploy_challenge() { local DOMAIN="${1}" TOKEN_FILENAME="${2}" TOKEN_VALUE="${3}" TLD_DOMAIN=$(tld_domain $DOMAIN) lexicon transip create ${TLD_DOMAIN} TXT --name="_acme-challenge.${DOMAIN}." --content="$TOKEN_VALUE" } clean_challenge() { local DOMAIN="${1}" TOKEN_FILENAME="${2}" TOKEN_VALUE="${3}" TLD_DOMAIN=$(tld_domain $DOMAIN) #lexicon transip delete ${TLD_DOMAIN} TXT --name="_acme-challenge.${DOMAIN}." } deploy_cert() { local DOMAIN="${1}" KEYFILE="${2}" CERTFILE="${3}" FULLCHAINFILE="${4}" CHAINFILE="${5}" TIMESTAMP="${6}" systemctl restart home-assistant@homeassistant systemctl restart apache2.service } unchanged_cert() { local DOMAIN="${1}" KEYFILE="${2}" CERTFILE="${3}" FULLCHAINFILE="${4}" CHAINFILE="${5}" } invalid_challenge() { local DOMAIN="${1}" RESPONSE="${2}" } request_failure() { local STATUSCODE="${1}" REASON="${2}" REQTYPE="${3}" } exit_hook() { : } HANDLER="$1"; shift if [[ "${HANDLER}" =~ ^(deploy_challenge|clean_challenge|deploy_cert|unchanged_cert|invalid_challenge|request_failure|exit_hook)$ ]]; then "$HANDLER" "$@" fi ``` Any idea what's going wrong here?
adam closed this issue 2025-12-29 01:25:36 +01:00
Author
Owner

@lukas2511 commented on GitHub (Feb 5, 2020):

I see in your hook.sh that your cleanup routine is actually commented out. Does your DNS provider maybe only support one TXT record for some reason and there just is an old one in there that never gets replaced?

Other than that you could add a few echos to the hook-script to see what values are actually given to lexicon, just to make sure that there isn't anything fundamentally broken.

@lukas2511 commented on GitHub (Feb 5, 2020): I see in your hook.sh that your cleanup routine is actually commented out. Does your DNS provider maybe only support one TXT record for some reason and there just is an old one in there that never gets replaced? Other than that you could add a few echos to the hook-script to see what values are actually given to lexicon, just to make sure that there isn't anything fundamentally broken.
Author
Owner

@ronaldtveen commented on GitHub (Feb 5, 2020):

I commented that out on purpose to not have it remove the record before I could check if it was the same as the token in the error, forgot to uncomment it here in the example.

I'll throw some echo's in later today and tinker with it and I'll report back when I know more.

@ronaldtveen commented on GitHub (Feb 5, 2020): I commented that out on purpose to not have it remove the record before I could check if it was the same as the token in the error, forgot to uncomment it here in the example. I'll throw some echo's in later today and tinker with it and I'll report back when I know more.
Author
Owner

@ronaldtveen commented on GitHub (Feb 5, 2020):

@lukas2511 I just double checked and TransIP does allow multiple TXT records, so that's not the problem. Also, the clean_challenge() is actually doing its job removing the record, but I've (again) commented that out to be able to check the token in the content of my TXT record that is added.

I threw some echo's in deploy_challenge(), they seem to be what you would expect.
The TXT record is added to my DNS with the correct token.

pi@raspi:~ $ sudo /home/pi/dehydrated/dehydrated -c -f /etc/dehydrated/config
# INFO: Using main config file /etc/dehydrated/config
Processing sub.example.com
 + Signing domains...
 + Generating private key...
 + Generating signing request...
 + Requesting new certificate order from CA...
 + Received 1 authorizations URLs from the CA
 + Handling authorization for sub.example.com
 + 1 pending challenge(s)
 + Deploying challenge tokens...

===================================================
deploy_challenge()
Domain: sub.example.com
Token filename: t_2yUvdXu9nYUnRMIWvmlaj0cBoB_g6rfHgBYjFeLB3
Token: Ma0-0d3P0y7GJaMcTQKRiezm6IcfUnNA3uRBdbazzUA
===================================================

RESULT
------
True
 + Responding to challenge for sub.example.com authorization...
 + Cleaning challenge tokens...
 + Challenge validation has failed :(
ERROR: Challenge is invalid! (returned: invalid) (result: {
  "type": "dns-01",
  "status": "invalid",
  "error": {
    "type": "urn:ietf:params:acme:error:unauthorized",
    "detail": "Incorrect TXT record \"0RfcthTmzfX_1I6aZ1hMXTAmmIxV2ztaHGa67qVyHdY\" found at _acme-challenge.sub.example.com",
    "status": 403
  },
  "url": "https://acme-v02.api.letsencrypt.org/acme/chall-v3/2661027905/JpVbcA",
  "token": "t_2yUvdXu9nYUnRMIWvmlaj0cBoB_g6rfHgBYjFeLB3"
})

What I don't get is this part of the log:

"detail": "Incorrect TXT record \"0RfcthTmzfX_1I6aZ1hMXTAmmIxV2ztaHGa67qVyHdY\" found at _acme-challenge.sub.example.com",

The record is still in my DNS zone, and that is not that record that is logged out but the actual token: _acme-challenge.sub 3600 TXT Ma0-0d3P0y7GJaMcTQKRiezm6IcfUnNA3uRBdbazzUA

@ronaldtveen commented on GitHub (Feb 5, 2020): @lukas2511 I just double checked and TransIP does allow multiple TXT records, so that's not the problem. Also, the clean_challenge() is actually doing its job removing the record, but I've (again) commented that out to be able to check the token in the content of my TXT record that is added. I threw some echo's in deploy_challenge(), they seem to be what you would expect. The TXT record is added to my DNS with the correct token. ``` pi@raspi:~ $ sudo /home/pi/dehydrated/dehydrated -c -f /etc/dehydrated/config # INFO: Using main config file /etc/dehydrated/config Processing sub.example.com + Signing domains... + Generating private key... + Generating signing request... + Requesting new certificate order from CA... + Received 1 authorizations URLs from the CA + Handling authorization for sub.example.com + 1 pending challenge(s) + Deploying challenge tokens... =================================================== deploy_challenge() Domain: sub.example.com Token filename: t_2yUvdXu9nYUnRMIWvmlaj0cBoB_g6rfHgBYjFeLB3 Token: Ma0-0d3P0y7GJaMcTQKRiezm6IcfUnNA3uRBdbazzUA =================================================== RESULT ------ True + Responding to challenge for sub.example.com authorization... + Cleaning challenge tokens... + Challenge validation has failed :( ERROR: Challenge is invalid! (returned: invalid) (result: { "type": "dns-01", "status": "invalid", "error": { "type": "urn:ietf:params:acme:error:unauthorized", "detail": "Incorrect TXT record \"0RfcthTmzfX_1I6aZ1hMXTAmmIxV2ztaHGa67qVyHdY\" found at _acme-challenge.sub.example.com", "status": 403 }, "url": "https://acme-v02.api.letsencrypt.org/acme/chall-v3/2661027905/JpVbcA", "token": "t_2yUvdXu9nYUnRMIWvmlaj0cBoB_g6rfHgBYjFeLB3" }) ``` What I don't get is this part of the log: ``` "detail": "Incorrect TXT record \"0RfcthTmzfX_1I6aZ1hMXTAmmIxV2ztaHGa67qVyHdY\" found at _acme-challenge.sub.example.com", ``` The record is still in my DNS zone, and that is **not** that record that is logged out but the actual token: ` _acme-challenge.sub 3600 TXT Ma0-0d3P0y7GJaMcTQKRiezm6IcfUnNA3uRBdbazzUA`
Author
Owner

@lukas2511 commented on GitHub (Feb 6, 2020):

DNS entries usually take some time to propagate and caches have to be cleared. Does lexicon wait until the entry has been propagated to all servers? Also at least Let's Encrypt caches DNS entries for up to 5 minutes iirc, so if you try to run it quickly in succession it may fail because of that.

@lukas2511 commented on GitHub (Feb 6, 2020): DNS entries usually take some time to propagate and caches have to be cleared. Does lexicon wait until the entry has been propagated to all servers? Also at least Let's Encrypt caches DNS entries for up to 5 minutes iirc, so if you try to run it quickly in succession it may fail because of that.
Author
Owner

@jahir commented on GitHub (Feb 6, 2020):

FWIW, dedyn.io provides a certboot hook script, which I modified for dehydrated, to do just that: call the dns provider API and ask their (primary) dns server until the change is spread.

@jahir commented on GitHub (Feb 6, 2020): FWIW, dedyn.io provides a [certboot hook script](https://desec.readthedocs.io/en/latest/dyndns/lets-encrypt.html), which I [modified for dehydrated](https://gist.github.com/jahir/9c12f3ca4e8990fc669b0131ea7c0752), to do just that: call the dns provider API and ask their (primary) dns server until the change is spread.
Author
Owner

@ronaldtveen commented on GitHub (Feb 6, 2020):

Ahh yes, that made sense.
Adding a sleep 60 right after lexicon added the record was long enough of a buffer that the new record could be resolved by LetsEncrypt.

Awesome guys, it’s all good now.

@ronaldtveen commented on GitHub (Feb 6, 2020): Ahh yes, that made sense. Adding a `sleep 60` right after lexicon added the record was long enough of a buffer that the new record could be resolved by LetsEncrypt. Awesome guys, it’s all good now.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: starred/dehydrated#457