I ran this command: $ sudo certbot certonly --manual --manual-auth-hook /etc/letsencrypt/acme-dns-auth.py --preferred-challenges
dns --debug-challenges -d *.vietnamthink.today -d vietnamthink.today -v
It produced this output:
Requesting a certificate for *.vietnamthink.today and vietnamthink.today
Performing the following challenges:
dns-01 challenge for vietnamthink.today
Running manual-auth-hook command: /etc/letsencrypt/acme-dns-auth.py
Challenges loaded. Press continue to submit to CA. Pass "-v" for more info about
challenges.
Press Enter to Continue
Waiting for verification...
Challenge failed for domain vietnamthink.today
dns-01 challenge for vietnamthink.today
Certbot failed to authenticate some domains (authenticator: manual). The Certificate Authority reported these problems:
Domain: vietnamthink.today
Type: unauthorized
Detail: Incorrect TXT record "zX2iuDDspbr-gRwMJs6mI52OpTPGKfYERloxEyd62rs" found at _acme-challenge.vietnamthink.today
Hint: The Certificate Authority failed to verify the DNS TXT records created by the --manual-auth-hook. Ensure that this hook
is functioning correctly and that it waits a sufficient duration of time for DNS propagation. Refer to "certbot --help manual" and the Certbot User Guide.
Cleaning up challenges
Some challenges have failed.
Ask for help or search for solutions at https://community.letsencrypt.org. See the logfile /var/log/letsencrypt/letsencrypt.log or re-run Certbot with -v for more details.
My web server is (include version): Nginx 1.18.0 (ubuntu 22.04)
The operating system my web server runs on is (include version):
My hosting provider, if applicable, is: my server at home with NAT
I can login to a root shell on my machine (yes or no, or I don't know): yes
I'm using a control panel to manage my site (no, or provide the name and version of the control panel): no
A DNS-01 challenge request for two "names" will require two TXT record entries.
Using --manual will force certbot to prompt you for each action and guide you every step of the way.
Since you are having trouble, and will likely be testing things for a bit until you get it right, you should do your testing against the staging system [use: --staging].
Once all tests are passed, then you can switch to production [removing --staging from the working test].
I'd start by removing the existing TXT record.
Then slowly follow the prompts and verify all steps while using the staging environment until you are confident your testing is complete.
How is /etc/letsencrypt/acme-dns-auth.py configured? It suggests that you're using acme-dns, but the fact you can actually delete a TXT record at Afraid is kinda weird if you'd use acme-dns.. Contradictory..
!/usr/bin/env python3
import json
import os
import requests
import sys
### EDIT THESE: Configuration values ###
# URL to acme-dns instance
ACMEDNS_URL = "https://auth.acme-dns.io"
# Path for acme-dns credential storage
STORAGE_PATH = "/etc/letsencrypt/acmedns.json"
# Whitelist for address ranges to allow the updates from
# Example: ALLOW_FROM = ["192.168.10.0/24", "::1/128"]
ALLOW_FROM = []
# Force re-registration. Overwrites the already existing acme-dns accounts.
FORCE_REGISTER = False
### DO NOT EDIT BELOW THIS POINT ###
### HERE BE DRAGONS ###
DOMAIN = os.environ["CERTBOT_DOMAIN"]
if DOMAIN.startswith("*."):
DOMAIN = DOMAIN[2:]
VALIDATION_DOMAIN = "_acme-challenge."+DOMAIN
VALIDATION_TOKEN = os.environ["CERTBOT_VALIDATION"]
class AcmeDnsClient(object):
"""
Handles the communication with ACME-DNS API
"""
def __init__(self, acmedns_url):
self.acmedns_url = acmedns_url
def register_account(self, allowfrom):
"""Registers a new ACME-DNS account"""
if allowfrom:
# Include whitelisted networks to the registration call
reg_data = {"allowfrom": allowfrom}
res = requests.post(self.acmedns_url+"/register",
data=json.dumps(reg_data))
else:
res = requests.post(self.acmedns_url+"/register")
if res.status_code == 201:
# The request was successful
return res.json()
else:
# Encountered an error
msg = ("Encountered an error while trying to register a new acme-dns "
"account. HTTP status {}, Response body: {}")
print(msg.format(res.status_code, res.text))
sys.exit(1)
def update_txt_record(self, account, txt):
"""Updates the TXT challenge record to ACME-DNS subdomain."""
update = {"subdomain": account['subdomain'], "txt": txt}
headers = {"X-Api-User": account['username'],
"X-Api-Key": account['password'],
"Content-Type": "application/json"}
res = requests.post(self.acmedns_url+"/update",
headers=headers,
data=json.dumps(update))
if res.status_code == 200:
# Successful update
return
else:
msg = ("Encountered an error while trying to update TXT record in "
"acme-dns. \n"
"------- Request headers:\n{}\n"
"------- Request body:\n{}\n"
"------- Response HTTP status: {}\n"
"------- Response body: {}")
s_headers = json.dumps(headers, indent=2, sort_keys=True)
s_update = json.dumps(update, indent=2, sort_keys=True)
s_body = json.dumps(res.json(), indent=2, sort_keys=True)
print(msg.format(s_headers, s_update, res.status_code, s_body))
sys.exit(1)
class Storage(object):
def __init__(self, storagepath):
self.storagepath = storagepath
self._data = self.load()
def load(self):
"""Reads the storage content from the disk to a dict structure"""
data = dict()
filedata = ""
try:
with open(self.storagepath, 'r') as fh:
filedata = fh.read()
except IOError as e:
if os.path.isfile(self.storagepath):
# Only error out if file exists, but cannot be read
print("ERROR: Storage file exists but cannot be read")
sys.exit(1)
try:
data = json.loads(filedata)
except ValueError:
if len(filedata) > 0:
# Storage file is corrupted
print("ERROR: Storage JSON is corrupted")
sys.exit(1)
return data
def save(self):
"""Saves the storage content to disk"""
serialized = json.dumps(self._data)
try:
with os.fdopen(os.open(self.storagepath,
os.O_WRONLY | os.O_CREAT, 0o600), 'w') as fh:
fh.truncate()
fh.write(serialized)
except IOError as e:
print("ERROR: Could not write storage file.")
sys.exit(1)
def put(self, key, value):
"""Puts the configuration value to storage and sanitize it"""
# If wildcard domain, remove the wildcard part as this will use the
# same validation record name as the base domain
if key.startswith("*."):
key = key[2:]
self._data[key] = value
def fetch(self, key):
"""Gets configuration value from storage"""
try:
return self._data[key]
except KeyError:
return None
if __name__ == "__main__":
# Init
client = AcmeDnsClient(ACMEDNS_URL)
storage = Storage(STORAGE_PATH)
# Check if an account already exists in storage
account = storage.fetch(DOMAIN)
if FORCE_REGISTER or not account:
# Create and save the new account
account = client.register_account(ALLOW_FROM)
storage.put(DOMAIN, account)
storage.save()
# Display the notification for the user to update the main zone
msg = "Please add the following CNAME record to your main DNS zone:\n{}"
cname = "{} CNAME {}.".format(VALIDATION_DOMAIN, account["fulldomain"])
print(msg.format(cname))
# Update the TXT record in acme-dns instance
client.update_txt_record(account, VALIDATION_TOKEN)
Certbot failed to authenticate some domains (authenticator: manual). The Certificate Authority reported these problems:
Domain: vietnamthink.today
Type: unauthorized
Detail: Incorrect TXT record "1906fa54-XXXX-XXXX-a5ab-3db60907c0d4.auth.acme-dns.io" found at _acme-challenge.vietnamthink.today
Domain: vietnamthink.today
Type: unauthorized
Detail: Incorrect TXT record "1906fa54-XXXX-XXXX-a5ab-3db60907c0d4.auth.acme-dns.io" found at _acme-challenge.vietnamthink.today
Hint: The Certificate Authority failed to verify the DNS TXT records created by the --manual-auth-hook. Ensure that
this hook is functioning correctly and that it waits a sufficient duration of time for DNS propagation. Refer to "certbot --help manual" and the Certbot User Guide.
Cleaning up challenges
Some challenges have failed.
Ask for help or search for solutions at https://community.letsencrypt.org. See the logfile /var/log/letsencrypt/letsencrypt.log or re-run Certbot with -v for more details.
I think you may have skipped a step [CNAME _acme-challenge.vietnamthink.today to 1906fa54-xxxx-xxxx-a5ab-3db60907c0d4.auth.acme-dns.io]
OR
You don't fully understand how ACME-DNS.IO is used.
BTW, My system has two server, the web hosting server 192.168.1.111 (ubuntu 22.04, Nginx, Wordpress with Letsencrypt SSL) for www.vietnamthink.today/vietnamthink.today its ok; and the mail server (192.168.1.222 with ubuntu 22.04, Nginx, iRedMail) for mail.vietnamthink.today:8044/8443. Both behind NAT by ADSL (8044/8443 to mail server 192.168.1.222 and 80/443 NAT to web server 192.168.1.111)