Introduction
Wildcard certificates allow you to secure multiple subdomains with a single certificate (e.g., *.example.com). Traefik requires DNS-01 challenge for wildcard certificates, which involves creating DNS TXT records for domain validation. Errors typically stem from DNS provider misconfiguration, permission issues, or incorrect domain/SAN configuration.
Symptoms
Error messages in Traefik logs:
unable to generate certificate for *.example.com: acme: error: 400 :: urn:ietf:params:acme:error:malformed
DNS record not found: _acme-challenge.example.com
DNS challenge failed: no valid TXT record found
wildcard certificate requires DNS-01 challenge
certificate not found for *.example.comObservable indicators: - Wildcard certificate not appearing in acme.json - Browser shows certificate invalid for subdomains - DNS TXT records not created or incorrect - Certificate only covers root domain, not wildcard - Subdomains return certificate errors
Common Causes
- 1.HTTP-01 used for wildcard - Wildcard requires DNS-01 challenge
- 2.DNS provider not configured - Missing or wrong credentials
- 3.Insufficient DNS permissions - API token lacks DNS edit access
- 4.Domain/SAN mismatch - Incorrect configuration of main and sans
- 5.DNS propagation delay - Challenge checked before record visible
- 6.Wrong DNS zone - TXT record in wrong domain
- 7.Provider not supported - DNS provider not in Traefik's supported list
- 8.Multiple resolvers - DNS resolvers not configured correctly
Step-by-Step Fix
Step 1: Verify Wildcard Requires DNS-01
```bash # Check current certificate configuration curl -s http://localhost:8080/api/certificates | jq
# Check Traefik logs for challenge errors docker logs traefik 2>&1 | grep -i "wildcard|dns|challenge"
# Test DNS resolution dig _acme-challenge.example.com TXT dig _acme-challenge.sub.example.com TXT ```
Step 2: Configure DNS-01 Challenge Provider
# traefik.yml with Cloudflare DNS-01
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 environment variables
services:
traefik:
image: traefik:v3.0
environment:
# Cloudflare options (use one):
# Option 1: API Token (recommended)
- CF_DNS_API_TOKEN=your-cloudflare-dns-api-token
# Option 2: Global API Key + Email
# - CF_API_EMAIL=admin@example.com
# - CF_API_KEY=your-cloudflare-global-api-key
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"Step 3: Configure Other DNS Providers
```yaml # Route53 (AWS) services: traefik: environment: - AWS_ACCESS_KEY_ID=your-access-key - AWS_SECRET_ACCESS_KEY=your-secret-key - AWS_REGION=us-east-1 command: - "--certificatesresolvers.route53.acme.dnschallenge.provider=route53"
# DigitalOcean services: traefik: environment: - DO_AUTH_TOKEN=your-digitalocean-token command: - "--certificatesresolvers.digitalocean.acme.dnschallenge.provider=digitalocean"
# Google Cloud DNS services: traefik: environment: - GCE_PROJECT=your-project-id - GOOGLE_APPLICATION_CREDENTIALS=/path/to/service-account.json command: - "--certificatesresolvers.gcloud.acme.dnschallenge.provider=gcloud"
# Azure DNS services: traefik: environment: - AZURE_CLIENT_ID=your-client-id - AZURE_CLIENT_SECRET=your-client-secret - AZURE_SUBSCRIPTION_ID=your-subscription-id - AZURE_TENANT_ID=your-tenant-id - AZURE_RESOURCE_GROUP=your-resource-group command: - "--certificatesresolvers.azure.acme.dnschallenge.provider=azure" ```
Step 4: Configure Wildcard Domain and SANs
# Docker labels for wildcard certificate
labels:
- "traefik.http.routers.myapp.rule=Host(`example.com`) || Host(`*.example.com`)"
- "traefik.http.routers.myapp.entrypoints=websecure"
- "traefik.http.routers.myapp.tls=true"
- "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"# Kubernetes IngressRoute for wildcard
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: wildcard-certificate
namespace: default
spec:
entryPoints:
- websecure
routes:
- match: Host(`example.com`) || HostRegexp(`{subdomain:[a-z]+}.example.com`)
kind: Rule
services:
- name: myapp
port: 80
tls:
certResolver: cloudflare
domains:
- main: example.com
sans:
- "*.example.com"Step 5: Configure for File Provider
# dynamic.yml - File provider
tls:
certificates:
- certFile: /etc/traefik/certs/wildcard.crt
keyFile: /etc/traefik/certs/wildcard.key
stores:
- default# Or let Traefik manage wildcard via certResolver
http:
routers:
wildcard:
rule: "Host(`example.com`) || HostRegexp(`{subdomain:[a-z]+}.example.com`)"
entryPoints:
- websecure
tls:
certResolver: cloudflare
domains:
- main: example.com
sans:
- "*.example.com"Step 6: Verify DNS API Permissions
```bash # Cloudflare - verify API token curl -X GET "https://api.cloudflare.com/client/v4/user/tokens/verify" \ -H "Authorization: Bearer your-cloudflare-api-token"
# Should return success with permissions
# Cloudflare - check zone permissions curl -X GET "https://api.cloudflare.com/client/v4/zones" \ -H "Authorization: Bearer your-cloudflare-api-token"
# Verify zone exists and token has DNS:Edit permission ```
# Cloudflare API Token needs these permissions:
# Zone - DNS - Edit
# Zone - Zone - ReadStep 7: Debug DNS Challenge
# Enable debug logging
# traefik.yml
log:
level: DEBUG```bash # Watch DNS challenge process docker logs -f traefik 2>&1 | grep -E "dns|challenge|txt|acme"
# Check if TXT record is created dig _acme-challenge.example.com TXT @1.1.1.1 dig _acme-challenge.example.com TXT @8.8.8.8
# Use DNS propagation checker # https://dnschecker.org/#TXT/_acme-challenge.example.com ```
```bash # Manual DNS challenge test # Check what TXT record Traefik expects docker logs traefik 2>&1 | grep -i "txt"
# Verify record exists nslookup -q=txt _acme-challenge.example.com ```
Step 8: Handle DNS Propagation Delay
# Increase propagation delay
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"
delayBeforeCheck: 30s # Wait before checking# Some providers need longer delay
# Check provider-specific documentationStep 9: Force Wildcard Certificate Generation
```bash # Remove existing certificate jq 'del(.cloudflare.Certificates[] | select(.domain.main == "example.com"))' \ /letsencrypt/acme.json > /tmp/acme.json && mv /tmp/acme.json /letsencrypt/acme.json
# Restart Traefik docker restart traefik
# Watch for new certificate request docker logs -f traefik 2>&1 | grep -i "example.com|wildcard" ```
Step 10: Verify Certificate
```bash # Check certificate details echo | openssl s_client -servername sub.example.com -connect sub.example.com:443 2>/dev/null | \ openssl x509 -noout -text | grep -A1 "Subject Alternative Name"
# Should show: DNS:example.com, DNS:*.example.com
# Test multiple subdomains curl -vI https://app.example.com 2>&1 | grep "SSL certificate" curl -vI https://api.example.com 2>&1 | grep "SSL certificate" curl -vI https://admin.example.com 2>&1 | grep "SSL certificate" ```
Advanced Diagnosis
Multiple Wildcard Certificates
# Multiple wildcards on same router
labels:
- "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.domains[1].main=example.org"
- "traefik.http.routers.myapp.tls.domains[1].sans=*.example.org"Cross-Domain Wildcards
# Wildcard with additional SANs
labels:
- "traefik.http.routers.myapp.tls.domains[0].main=*.example.com"
- "traefik.http.routers.myapp.tls.domains[0].sans=example.com,*.api.example.com"External Certificate Management
# Use cert-manager for Kubernetes
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: wildcard-example-com
namespace: traefik
spec:
secretName: wildcard-example-com-tls
issuerRef:
name: letsencrypt-prod
kind: ClusterIssuer
dnsNames:
- example.com
- "*.example.com"# Reference secret in IngressRoute
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: myapp
spec:
tls:
secretName: wildcard-example-com-tlsCheck Supported DNS Providers
```bash # List of supported providers # https://doc.traefik.io/traefik/https/acme/#providers
# Common providers: # - cloudflare # - route53 # - digitalocean # - gcloud # - azure # - gandiv5 # - ovh # - linode # - namecheap # - godaddy # - hetzner ```
Debug DNS Provider Issues
```bash # Check if provider environment is set docker exec traefik env | grep -E "CF_|AWS_|DO_"
# Check Traefik logs for provider errors docker logs traefik 2>&1 | grep -i "provider|dns"
# Test API access manually (Cloudflare example) curl -X POST "https://api.cloudflare.com/client/v4/zones/ZONE_ID/dns_records" \ -H "Authorization: Bearer YOUR_TOKEN" \ -H "Content-Type: application/json" \ --data '{"type":"TXT","name":"_acme-challenge.example.com","content":"test","ttl":120}' ```
Common Pitfalls
- Using HTTP-01 for wildcards - Wildcards MUST use DNS-01
- Wrong API token - Cloudflare needs DNS API token, not Global API key
- Missing SANs - Need both main domain and wildcard in SANs
- DNS propagation delay - Allow time for TXT record to propagate
- Wrong zone - TXT record in wrong DNS zone
- Token permissions - API token needs DNS:Edit permission
- Multiple certificates - Same certificate used across routers
- CNAME records - May need to verify actual zone, not CNAME target
Best Practices
```yaml # Complete wildcard certificate configuration version: "3.8"
services: traefik: image: traefik:v3.0 environment: - CF_DNS_API_TOKEN=${CLOUDFLARE_DNS_TOKEN} command: - "--api.dashboard=true" - "--providers.docker=true" - "--providers.docker.exposedbydefault=false" - "--entrypoints.web.address=:80" - "--entrypoints.web.http.redirections.entryPoint.to=websecure" - "--entrypoints.web.http.redirections.entryPoint.scheme=https" - "--entrypoints.websecure.address=:443" # DNS-01 for wildcards - "--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" - "--certificatesresolvers.cloudflare.acme.dnschallenge.delaybeforecheck=10s" ports: - "80:80" - "443:443" volumes: - /var/run/docker.sock:/var/run/docker.sock:ro - ./letsencrypt:/letsencrypt
app:
image: myapp:latest
labels:
- "traefik.enable=true"
- "traefik.http.routers.app.rule=Host(example.com) || HostRegexp({subdomain:[a-z]+}.example.com)"
- "traefik.http.routers.app.entrypoints=websecure"
- "traefik.http.routers.app.tls=true"
- "traefik.http.routers.app.tls.certresolver=cloudflare"
- "traefik.http.routers.app.tls.domains[0].main=example.com"
- "traefik.http.routers.app.tls.domains[0].sans=*.example.com"
```
Related Issues
- Traefik Let's Encrypt Error
- Traefik SSL Certificate Error
- Traefik Start Failed
- Traefik Configuration Error