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:

bash
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.com

Observable 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. 1.HTTP-01 used for wildcard - Wildcard requires DNS-01 challenge
  2. 2.DNS provider not configured - Missing or wrong credentials
  3. 3.Insufficient DNS permissions - API token lacks DNS edit access
  4. 4.Domain/SAN mismatch - Incorrect configuration of main and sans
  5. 5.DNS propagation delay - Challenge checked before record visible
  6. 6.Wrong DNS zone - TXT record in wrong domain
  7. 7.Provider not supported - DNS provider not in Traefik's supported list
  8. 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

yaml
# 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"
yaml
# 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

yaml
# 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"
yaml
# 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

yaml
# dynamic.yml - File provider
tls:
  certificates:
    - certFile: /etc/traefik/certs/wildcard.crt
      keyFile: /etc/traefik/certs/wildcard.key
      stores:
        - default
yaml
# 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 ```

yaml
# Cloudflare API Token needs these permissions:
# Zone - DNS - Edit
# Zone - Zone - Read

Step 7: Debug DNS Challenge

bash
# 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

yaml
# 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
bash
# Some providers need longer delay
# Check provider-specific documentation

Step 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

yaml
# 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

yaml
# 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

yaml
# 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"
yaml
# Reference secret in IngressRoute
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
  name: myapp
spec:
  tls:
    secretName: wildcard-example-com-tls

Check 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" ```

  • Traefik Let's Encrypt Error
  • Traefik SSL Certificate Error
  • Traefik Start Failed
  • Traefik Configuration Error