Introduction
Traefik SSL certificate errors prevent secure HTTPS connections to your services. Common issues include Let's Encrypt ACME challenge failures, certificate not found errors, TLS handshake failures, and misconfigured certificate resolvers. These errors result in browsers showing security warnings or connections being rejected entirely.
Symptoms
Error messages in Traefik logs:
Unable to generate certificate: acme: error: 403 :: urn:ietf:params:acme:error:unauthorized
"no certificate found for domain"
"TLS handshake error"
"certificate has expired"
"challenge failed: dns-01"Client-facing symptoms: - Browser shows "Your connection is not private" warning - NET::ERR_CERT_AUTHORITY_INVALID error - Certificate shows as invalid or expired - HTTPS requests fail with connection errors - HTTP works but HTTPS does not
Common Causes
- 1.ACME challenge failure - HTTP-01 or DNS-01 challenge not working
- 2.Certificate resolver not configured - No certresolver on router
- 3.Wrong email address - ACME registration email invalid
- 4.Rate limiting - Let's Encrypt rate limits exceeded
- 5.DNS propagation delay - DNS-01 challenge fails before propagation
- 6.Port 80 blocked - HTTP-01 challenge cannot reach Traefik
- 7.Storage permissions - Cannot write to acme.json
- 8.Wildcard certificate issues - DNS-01 not configured for wildcards
Step-by-Step Fix
Step 1: Check Certificate Status
```bash # Check Traefik API for certificates curl http://localhost:8080/api/certificates | jq
# Check acme.json content cat /letsencrypt/acme.json | jq
# Check Traefik logs for ACME errors docker logs traefik 2>&1 | grep -i "acme|certificate|tls"
# Check certificate expiration curl -vk https://app.example.com/ 2>&1 | grep -A5 "Server certificate" ```
Step 2: Verify Certificate Resolver Configuration
# traefik.yml static configuration
certificatesResolvers:
myresolver:
acme:
email: admin@example.com
storage: /letsencrypt/acme.json
httpChallenge:
entryPoint: web# For DNS-01 challenge (required for wildcards)
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"Step 3: Fix HTTP-01 Challenge
```yaml # Complete configuration for HTTP-01 challenge entryPoints: web: address: ":80" websecure: address: ":443"
certificatesResolvers: myresolver: acme: email: admin@example.com storage: /letsencrypt/acme.json httpChallenge: entryPoint: web
# Docker labels for router
labels:
- "traefik.http.routers.myapp.rule=Host(app.example.com)"
- "traefik.http.routers.myapp.entrypoints=websecure"
- "traefik.http.routers.myapp.tls=true"
- "traefik.http.routers.myapp.tls.certresolver=myresolver"
```
```bash # Verify HTTP-01 challenge works curl http://app.example.com/.well-known/acme-challenge/test
# Check port 80 is accessible externally curl -v http://app.example.com/ ```
Step 4: Fix DNS-01 Challenge for Cloudflare
# docker-compose.yml
services:
traefik:
image: traefik:v3.0
environment:
- CF_API_EMAIL=admin@example.com
- CF_API_KEY=your-cloudflare-api-key
# Or use CF_DNS_API_TOKEN for scoped token
- 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"
- "--certificatesResolvers.cloudflare.acme.dnsChallenge.resolvers=1.1.1.1:53,8.8.8.8:53"# Kubernetes Secret for Cloudflare
apiVersion: v1
kind: Secret
metadata:
name: cloudflare-api-token
namespace: traefik
type: Opaque
stringData:
CF_DNS_API_TOKEN: your-cloudflare-api-tokenStep 5: Fix Wildcard Certificates
# Wildcard certificates require DNS-01 challenge
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"Step 6: Fix Storage Permissions
```bash # Check acme.json permissions ls -la /letsencrypt/acme.json
# Should be readable/writable by Traefik chmod 600 /letsencrypt/acme.json chown root:root /letsencrypt/acme.json
# Or if running as specific user chown 1000:1000 /letsencrypt/acme.json ```
Step 7: Handle Rate Limits
```bash # Check Let's Encrypt rate limits # 50 certificates per registered domain per week # 5 failed validation attempts per account per hour
# For testing, use Let's Encrypt staging certificatesResolvers: myresolver: acme: caserver: https://acme-staging-v02.api.letsencrypt.org/directory email: admin@example.com storage: /letsencrypt/acme-staging.json ```
Step 8: Verify and Test
```bash # Restart Traefik to retry certificate docker restart traefik
# Watch for certificate generation docker logs -f traefik 2>&1 | grep -i "certificate|acme"
# Test HTTPS connection 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 -dates ```
Advanced Diagnosis
Manual Certificate Request
```bash # Force certificate renewal by deleting acme.json rm /letsencrypt/acme.json docker restart traefik
# Or use lego directly for testing lego --email=admin@example.com --domains=app.example.com --http run ```
Debug ACME Challenge
```yaml # Enable debug logging log: level: DEBUG
# Add access logs accessLog: filePath: /var/log/traefik/access.log format: json ```
```bash # Check challenge endpoint curl -v http://app.example.com/.well-known/acme-challenge/test-token
# For DNS-01, check TXT record dig _acme-challenge.example.com TXT ```
Check Certificate Chain
```bash # Verify certificate chain openssl s_client -connect app.example.com:443 -servername app.example.com -showcerts
# Check for missing intermediate curl --head https://app.example.com/ ```
Custom Certificates
# Use custom certificate instead of Let's Encrypt
tls:
certificates:
- certFile: /etc/traefik/certs/cert.pem
keyFile: /etc/traefik/certs/key.pem
stores:
- default# Reference custom certificate
labels:
- "traefik.http.routers.myapp.tls=true"Common Pitfalls
- Using HTTP-01 for wildcards - Must use DNS-01 for wildcard certificates
- Wrong Cloudflare API key - Need DNS API token, not Global API key
- Port 80 not accessible - HTTP-01 requires port 80 from internet
- Staging certificate in production - Browser won't trust staging CA
- acme.json permissions - Traefik can't read/write certificate storage
- DNS propagation delay - DNS-01 fails before record is visible
- Email not verified - Check spam for ACME verification emails
Best Practices
```yaml # Complete production configuration version: "3.8"
services: traefik: image: traefik:v3.0 command: # Entrypoints - "--entrypoints.web.address=:80" - "--entrypoints.web.http.redirections.entryPoint.to=websecure" - "--entrypoints.web.http.redirections.entryPoint.scheme=https" - "--entrypoints.websecure.address=:443"
# Certificate resolver (Let's Encrypt) - "--certificatesResolvers.letsencrypt.acme.email=admin@example.com" - "--certificatesResolvers.letsencrypt.acme.storage=/letsencrypt/acme.json" - "--certificatesResolvers.letsencrypt.acme.httpChallenge.entryPoint=web"
# For DNS-01 with wildcards - "--certificatesResolvers.cloudflare.acme.email=admin@example.com" - "--certificatesResolvers.cloudflare.acme.storage=/letsencrypt/acme-cloudflare.json" - "--certificatesResolvers.cloudflare.acme.dnsChallenge.provider=cloudflare" - "--certificatesResolvers.cloudflare.acme.dnsChallenge.resolvers=1.1.1.1:53"
# Logging - "--log.level=INFO" - "--accesslog=true" ports: - "80:80" - "443:443" volumes: - /var/run/docker.sock:/var/run/docker.sock:ro - ./letsencrypt:/letsencrypt environment: - CF_DNS_API_TOKEN=${CLOUDFLARE_API_TOKEN}
myapp:
image: myapp:latest
labels:
- "traefik.enable=true"
- "traefik.http.routers.myapp.rule=Host(app.example.com)"
- "traefik.http.routers.myapp.entrypoints=websecure"
- "traefik.http.routers.myapp.tls=true"
- "traefik.http.routers.myapp.tls.certresolver=letsencrypt"
```
Related Issues
- Traefik Routing Not Working
- HAProxy SSL Handshake Failed
- AWS ALB SSL Certificate Error
- 502 Bad Gateway