Introduction
Let's Encrypt certificate errors in Traefik prevent your services from establishing secure HTTPS connections. These errors can occur during initial certificate issuance, renewal, or when ACME challenges fail. Common issues include HTTP-01 challenge failures, DNS-01 misconfiguration, rate limiting, and storage permission problems.
Symptoms
Error messages in Traefik logs:
Unable to generate certificate: acme: error: 403 :: urn:ietf:params:acme:error:unauthorized
ACME challenge failed: http-01: no reachable port
certificate renewal failed: rate limited
error getting certificate: acme: cannot register account
ACME account registration failed: 400 :: urn:ietf:params:acme:error:malformedObservable indicators: - Browser shows certificate warning or NET::ERR_CERT_AUTHORITY_INVALID - Certificates not appearing in acme.json - Let's Encrypt validation times out - Certificate shows as self-signed or expired - HTTPS connections fail while HTTP works
Common Causes
- 1.HTTP-01 challenge blocked - Port 80 not accessible from internet
- 2.DNS-01 misconfigured - Wrong provider or credentials
- 3.Rate limits exceeded - Too many certificate requests
- 4.acme.json permissions - Cannot write to certificate storage
- 5.Email address invalid - ACME registration email issues
- 6.CAA records blocking - DNS CAA record prevents Let's Encrypt
- 7.Firewall blocking - Security group or firewall rules
- 8.Staging vs production - Using staging CA in production
Step-by-Step Fix
Step 1: Check Certificate Status
```bash # Check acme.json content cat /letsencrypt/acme.json | jq '.[] | .Certificates[] | {domain: .domain.main, notAfter: .certificate}'
# Check via Traefik API curl http://localhost:8080/api/certificates | jq
# Check certificate expiration echo | openssl s_client -servername app.example.com -connect app.example.com:443 2>/dev/null | openssl x509 -noout -dates
# Check certificate issuer echo | openssl s_client -servername app.example.com -connect app.example.com:443 2>/dev/null | openssl x509 -noout -issuer
# Watch Traefik logs for ACME errors docker logs -f traefik 2>&1 | grep -i "acme|certificate|letsencrypt" ```
Step 2: Verify Certificate Resolver Configuration
# traefik.yml - Correct Let's Encrypt configuration
certificatesResolvers:
letsencrypt:
acme:
email: admin@example.com
storage: /letsencrypt/acme.json
httpChallenge:
entryPoint: web# Docker Compose with Let's Encrypt
services:
traefik:
image: traefik:v3.0
command:
- "--certificatesresolvers.letsencrypt.acme.email=admin@example.com"
- "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
- "--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web"
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
ports:
- "80:80" # Required for HTTP-01 challenge
- "443:443"
volumes:
- ./letsencrypt:/letsencryptStep 3: Fix HTTP-01 Challenge
```bash # Test if port 80 is accessible from outside curl -v http://app.example.com/.well-known/acme-challenge/test
# Should return 404 (not connection refused) # If connection refused, check firewall and port forwarding
# Check if Traefik is listening on port 80 netstat -tlnp | grep :80 docker port traefik | grep 80
# Test from external service # Use online tools like: # https://tools.keycdn.com/curl # curl -v http://app.example.com/.well-known/acme-challenge/test ```
# Ensure port 80 is exposed and not redirected
entryPoints:
web:
address: ":80"
# Don't redirect for ACME challenge
# Or use middleware to exclude .well-known# If using redirect, exclude ACME challenge path
http:
routers:
acme:
rule: "PathPrefix(`/.well-known/acme-challenge/`)"
entryPoints:
- web
service: acme@internalStep 4: Fix DNS-01 Challenge
# Cloudflare DNS-01 configuration
certificatesResolvers:
cloudflare:
acme:
email: admin@example.com
storage: /letsencrypt/acme.json
dnsChallenge:
provider: cloudflare
resolvers:
- "1.1.1.1:53"
- "8.8.8.8:53"# Docker Compose with Cloudflare
services:
traefik:
image: traefik:v3.0
environment:
- CF_DNS_API_TOKEN=your-cloudflare-api-token
command:
- "--certificatesresolvers.cloudflare.acme.email=admin@example.com"
- "--certificatesresolvers.cloudflare.acme.storage=/letsencrypt/acme.json"
- "--certificatesresolvers.cloudflare.acme.dnschallenge.provider=cloudflare"```bash # Verify DNS record is created dig _acme-challenge.example.com TXT
# Check Cloudflare API token permissions # Token must have DNS:Edit permission for zone ```
Step 5: Fix Storage Permissions
```bash # Check acme.json permissions ls -la /letsencrypt/acme.json # Should be -rw------- (600)
# Create if missing touch /letsencrypt/acme.json chmod 600 /letsencrypt/acme.json
# Check directory permissions ls -la /letsencrypt/ # Traefik must be able to write here
# For Docker docker exec traefik ls -la /letsencrypt/ docker exec traefik cat /etc/passwd | grep traefik ```
Step 6: Handle Rate Limits
```bash # Let's Encrypt rate limits: # - 50 certificates per registered domain per week # - 5 failed validation attempts per account per hour # - 300 new orders per account per 3 hours
# Check if rate limited # Error will mention "rate limited" or "too many certificates"
# Use staging for testing ```
# Staging environment for testing
certificatesResolvers:
letsencrypt-staging:
acme:
caServer: https://acme-staging-v02.api.letsencrypt.org/directory
email: admin@example.com
storage: /letsencrypt/acme-staging.json
httpChallenge:
entryPoint: web# Check certificate count per domain
curl -s https://crt.sh/?q=example.com | grep -c "ID"Step 7: Fix Email Issues
```yaml # Ensure valid email certificatesResolvers: letsencrypt: acme: email: admin@example.com # Must be valid for Let's Encrypt storage: /letsencrypt/acme.json
# Email is used for: # 1. Account registration # 2. Expiration reminders # 3. Important notifications ```
# Clear account and re-register if needed
rm /letsencrypt/acme.json
docker restart traefikStep 8: Check CAA Records
```bash # Check CAA records dig example.com CAA
# Should allow Let's Encrypt: # example.com. IN CAA 0 issue "letsencrypt.org"
# If CAA blocks Let's Encrypt, add: # example.com. IN CAA 0 issue "letsencrypt.org" # example.com. IN CAA 0 issuewild "letsencrypt.org" ```
Step 9: Force Certificate Renewal
```bash # Delete certificate to force renewal jq 'del(.letsencrypt.Certificates[] | select(.domain.main == "app.example.com"))' \ /letsencrypt/acme.json > /tmp/acme.json && mv /tmp/acme.json /letsencrypt/acme.json
# Or delete entire acme.json for fresh start rm /letsencrypt/acme.json touch /letsencrypt/acme.json chmod 600 /letsencrypt/acme.json docker restart traefik
# Watch for new certificate docker logs -f traefik 2>&1 | grep -i "certificate|acme" ```
Step 10: Verify and Test
```bash # Restart Traefik docker restart traefik
# Check logs docker logs -f traefik 2>&1 | grep -i "certificate|acme"
# Test HTTPS curl -vk https://app.example.com/
# Check certificate details echo | openssl s_client -servername app.example.com -connect app.example.com:443 2>/dev/null | \ openssl x509 -noout -text | grep -A2 "Issuer|Subject Alternative Name" ```
Advanced Diagnosis
Manual ACME Testing
```bash # Use certbot for testing certbot certonly --dry-run -d app.example.com --standalone
# Use lego directly lego --email=admin@example.com --domains=app.example.com --http run ```
Debug ACME Process
# Enable debug logging
log:
level: DEBUG# Watch detailed ACME process
docker logs -f traefik 2>&1 | grep -E "acme|challenge|certificate"Check for Proxy Chain Issues
```bash # If behind another proxy, check X-Forwarded headers curl -v http://app.example.com/.well-known/acme-challenge/test
# May need to trust forwarded headers entryPoints: web: address: ":80" forwardedHeaders: trustedIPs: - "10.0.0.0/8" - "172.16.0.0/12" ```
Multiple Domains
# Certificate for multiple domains
labels:
- "traefik.http.routers.myapp.tls.domains[0].main=example.com"
- "traefik.http.routers.myapp.tls.domains[0].sans=app.example.com,api.example.com"
- "traefik.http.routers.myapp.tls.certresolver=letsencrypt"Wildcard Certificates
# Wildcard requires DNS-01
certificatesResolvers:
cloudflare:
acme:
email: admin@example.com
storage: /letsencrypt/acme.json
dnsChallenge:
provider: cloudflare# Router configuration for wildcard
labels:
- "traefik.http.routers.myapp.rule=Host(`example.com`) || Host(`*.example.com`)"
- "traefik.http.routers.myapp.tls.domains[0].main=example.com"
- "traefik.http.routers.myapp.tls.domains[0].sans=*.example.com"
- "traefik.http.routers.myapp.tls.certresolver=cloudflare"Common Pitfalls
- Using HTTP-01 for wildcards - Must use DNS-01 for wildcard certificates
- Port 80 not accessible - HTTP-01 requires port 80 from internet
- Wrong DNS API credentials - Check token permissions and format
- Staging certificate in production - Browser won't trust staging CA
- Redirecting port 80 - ACME challenge must not redirect
- CAA record blocking - DNS CAA may prevent Let's Encrypt
- Rate limiting - Too many requests triggers Let's Encrypt limits
- Clock skew - Server time incorrect affects certificate validation
Best Practices
```yaml # Complete production Let's Encrypt setup version: "3.8"
services:
traefik:
image: traefik:v3.0
command:
- "--api.dashboard=true"
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false"
# Entrypoints
- "--entrypoints.web.address=:80"
- "--entrypoints.web.http.redirections.entryPoint.to=websecure"
- "--entrypoints.web.http.redirections.entryPoint.scheme=https"
- "--entrypoints.websecure.address=:443"
# Let's Encrypt HTTP-01
- "--certificatesresolvers.letsencrypt.acme.email=admin@example.com"
- "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
- "--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web"
# DNS-01 for wildcards (alternative)
# - "--certificatesresolvers.cloudflare.acme.email=admin@example.com"
# - "--certificatesresolvers.cloudflare.acme.storage=/letsencrypt/acme-cloudflare.json"
# - "--certificatesresolvers.cloudflare.acme.dnschallenge.provider=cloudflare"
# Logging
- "--log.level=INFO"
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./letsencrypt:/letsencrypt
environment:
- CF_DNS_API_TOKEN=${CLOUDFLARE_TOKEN}
labels:
- "traefik.enable=true"
- "traefik.http.routers.dashboard.rule=Host(traefik.example.com)"
- "traefik.http.routers.dashboard.entrypoints=websecure"
- "traefik.http.routers.dashboard.tls.certresolver=letsencrypt"
- "traefik.http.routers.dashboard.service=api@internal"
app:
image: myapp:latest
labels:
- "traefik.enable=true"
- "traefik.http.routers.app.rule=Host(app.example.com)"
- "traefik.http.routers.app.entrypoints=websecure"
- "traefik.http.routers.app.tls.certresolver=letsencrypt"
```
Related Issues
- Traefik SSL Certificate Error
- Traefik Wildcard Certificate Error
- Traefik Start Failed
- AWS ALB SSL Certificate Error