Introduction
Let's Encrypt validates domain ownership through ACME challenges before issuing certificates. The HTTP-01 challenge requires serving a specific token over HTTP, while DNS-01 requires creating a TXT record. Challenge failures prevent certificate issuance and often stem from network configuration, DNS issues, or server setup problems.
Symptoms
- Certbot fails with:
Failed to authorize domain - Error:
Challenge failed for domain example.com - HTTP-01:
The client lacks sufficient authorization - DNS-01:
DNS problem: DNS record not found - Multiple retry attempts all failing
- Challenge validation timeout errors
- Certificates stuck in pending state
Common Causes
- HTTP-01: Challenge path not accessible via HTTP
- HTTP-01: Server redirects challenge URL incorrectly
- HTTP-01: Firewall blocking port 80
- HTTP-01: Web server not serving challenge directory
- DNS-01: TXT record not created or propagated
- DNS-01: Wrong DNS zone updated
- DNS-01: API credentials for DNS provider incorrect
- DNS-01: DNS propagation delay too short
Step-by-Step Fix
Step 1: Identify Challenge Type and Error
```bash # Check certbot logs for specific error certbot certificates 2>&1 | tail -50
# Run with verbose output certbot certonly -v -d example.com
# Check challenge status certbot certificates | grep -A 5 "example.com" ```
Step 2: Fix HTTP-01 Challenge Failures
```bash # Test if challenge URL is reachable # Certbot creates token in webroot, CA tries to fetch it
# Check if port 80 is accessible curl -I http://example.com/.well-known/acme-challenge/test-token
# Test from external network (not localhost) curl -I http://example.com:80/
# Check firewall rules iptables -L -n | grep 80 ufw status | grep 80 ```
Common HTTP-01 fixes:
```bash # Ensure webroot is correct certbot certonly --webroot -w /var/www/html -d example.com
# Or use standalone mode (stop web server temporarily) systemctl stop nginx certbot certonly --standalone -d example.com systemctl start nginx
# Or use nginx/apache plugin certbot --nginx -d example.com certbot --apache -d example.com ```
Fix redirect issues:
```nginx # Make sure challenge path isn't redirected # Nginx config exception location /.well-known/acme-challenge/ { root /var/www/html; # Don't redirect this path }
# Before your HTTPS redirect: location / { return 301 https://$host$request_uri; } ```
# Verify challenge path works
mkdir -p /var/www/html/.well-known/acme-challenge/
echo "test-content" > /var/www/html/.well-known/acme-challenge/test-token
curl http://example.com/.well-known/acme-challenge/test-token
# Should return "test-content"Step 3: Fix DNS-01 Challenge Failures
```bash # Check if TXT record exists dig _acme-challenge.example.com TXT
# Check from authoritative server dig @ns1.example.com _acme-challenge.example.com TXT
# Check DNS propagation # Use online tools like: # https://dnschecker.org/#TXT/_acme-challenge.example.com ```
Manual DNS-01 fix:
```bash # Get TXT record value from certbot certbot certonly --manual --preferred-challenges dns -d example.com
# Certbot will show output like: # "Please deploy a DNS TXT record under the name: # _acme-challenge.example.com with the value: # abc123xyz..."
# Create TXT record in your DNS control panel # Wait for propagation (5-10 minutes typically) dig _acme-challenge.example.com TXT
# Press Enter in certbot to continue validation ```
Automated DNS-01 with Cloudflare:
```bash # Install Cloudflare plugin pip install certbot-dns-cloudflare
# Create credentials file cat > /root/.cloudflare.ini << EOF dns_cloudflare_api_token = your_api_token_here EOF chmod 600 /root/.cloudflare.ini
# Request certificate certbot certonly --dns-cloudflare \ --dns-cloudflare-credentials /root/.cloudflare.ini \ -d "*.example.com" -d "example.com" ```
With acme.sh for other providers:
```bash # Install acme.sh curl https://get.acme.sh | sh
# Cloudflare export CF_Token="your_api_token" acme.sh --issue -d "*.example.com" -d "example.com" --dns dns_cf
# Route53 export AWS_ACCESS_KEY_ID="xxx" export AWS_SECRET_ACCESS_KEY="xxx" acme.sh --issue -d "*.example.com" --dns dns_aws
# DigitalOcean export DO_API_TOKEN="xxx" acme.sh --issue -d "*.example.com" --dns dns_dgon
# GoDaddy export GD_Key="xxx" export GD_Secret="xxx" acme.sh --issue -d "*.example.com" --dns dns_gd ```
Step 4: Check Validation Timing
```bash # DNS propagation can take time # Wait before retrying
# For HTTP-01, ensure server is responding systemctl status nginx apache2
# For both, ensure no rate limiting from previous failures # 5 failures per hostname per hour limit ```
Step 5: Test with Staging Environment
```bash # Use staging to test without rate limits certbot certonly --staging --webroot -w /var/www/html -d example.com
# Or with acme.sh acme.sh --issue -d example.com --server letsencrypt_test
# Once working, switch to production certbot certonly --webroot -w /var/www/html -d example.com ```
Step 6: Verify Certificate Issuance
```bash # After successful challenge certbot certificates
# Check certificate details openssl x509 -in /etc/letsencrypt/live/example.com/cert.pem -noout -text
# Test HTTPS curl -vI https://example.com openssl s_client -connect example.com:443 -servername example.com ```
Common Pitfalls
- Redirecting HTTP to HTTPS before challenge path exception
- Firewall blocking port 80 while trying HTTP-01
- DNS propagation delay with DNS-01 challenges
- Using wrong DNS zone (subdomain vs root domain)
- Testing from localhost while external access differs
- Not waiting between failed validation retries
- Rate limit exhaustion from repeated failures
Best Practices
- Use staging environment (
--staging) for testing - Add redirect exceptions for
/.well-known/acme-challenge/ - Use DNS-01 for wildcard certificates and internal domains
- Automate DNS challenges with API plugins
- Test challenge accessibility from external network
- Monitor DNS propagation before proceeding
- Keep port 80 accessible even if HTTPS-only site
Related Issues
- Let's Encrypt Rate Limit Exceeded
- ACME HTTP-01 Challenge Failing Due to Redirect
- SSL Certificate Chain Incomplete
- SSL Auto-Renewal Not Working