This site is an archive and is no longer updated.
    Back to Guides
    Web Servers

    Traefik SSL Certificate Configuration

    Automatic HTTPS for your containers and services - from Let's Encrypt automation to enterprise PKI.

    15-18 min read
    LinkedInX
    Traefik SSL Certificate Configuration Guide

    Version note: This guide covers Traefik v3.x syntax. If you're using Traefik v2.x, some configuration keys differ (e.g., certificatesResolvers was certResolvers in some contexts). Check the v2 to v3 migration guide if upgrading.

    What makes Traefik different:

    • Automatic discovery - Traefik detects services via Docker labels, Kubernetes annotations, or config files
    • Built-in ACME - Native Let's Encrypt integration with automatic renewal
    • Dynamic configuration - Add or remove certificates without restarts
    • Multiple providers - Works with Docker, Kubernetes, file-based config, and more

    1. Traefik TLS Architecture

    Understanding how Traefik handles TLS helps you configure it correctly.

    Key Concepts

    ConceptPurpose
    EntrypointsWhere Traefik listens (ports 80, 443)
    RoutersMatch requests to services, decide HTTP vs HTTPS
    Certificate ResolversHow certificates are obtained (ACME, files)
    TLS StoresWhere certificates are stored and managed

    Traffic Flow

    Client → Entrypoint (443, TLS termination) → Router (routing decision) → Service (HTTP internally)

    TLS termination happens at the entrypoint level—Traefik decrypts incoming HTTPS traffic there. Routers then make routing decisions based on rules (Host, Path, Headers). Your backend services typically run unencrypted on the internal network—Traefik handles the HTTPS complexity.

    Static vs Dynamic Configuration:

    • Static config - Entrypoints, ACME resolvers, providers (set at startup)
    • Dynamic config - Routers, services, certificates, middlewares (can change at runtime)

    2. Automatic Certificates with Let's Encrypt

    This is Traefik's superpower—automatic, free SSL certificates with zero manual intervention.

    Basic ACME Configuration

    Static configuration (traefik.yml):

    entryPoints:
      web:
        address: ":80"
      websecure:
        address: ":443"
    
    certificatesResolvers:
      letsencrypt:
        acme:
          email: admin@example.com
          storage: /letsencrypt/acme.json
          httpChallenge:
            entryPoint: web
    SettingPurpose
    emailLet's Encrypt notifications (expiration warnings)
    storageFile where certificates are stored (persist this!)
    httpChallenge.entryPointWhich entrypoint handles ACME HTTP-01 challenges

    Docker Compose Example

    version: "3.9"
    
    services:
      traefik:
        image: traefik:v3.0
        command:
          - "--providers.docker=true"
          - "--providers.docker.exposedbydefault=false"
          - "--entrypoints.web.address=:80"
          - "--entrypoints.websecure.address=:443"
          - "--certificatesresolvers.letsencrypt.acme.httpchallenge=true"
          - "--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web"
          - "--certificatesresolvers.letsencrypt.acme.email=admin@example.com"
          - "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
        ports:
          - "80:80"
          - "443:443"
        volumes:
          - "./letsencrypt:/letsencrypt"
          - "/var/run/docker.sock:/var/run/docker.sock:ro"
    
      myapp:
        image: nginx:alpine
        labels:
          - "traefik.enable=true"
          - "traefik.http.routers.myapp.rule=Host(`app.example.com`)"
          - "traefik.http.routers.myapp.entrypoints=websecure"
          - "traefik.http.routers.myapp.tls.certresolver=letsencrypt"

    Critical:

    The letsencrypt volume must persist. If you lose acme.json, Traefik requests new certificates—and you might hit Let's Encrypt rate limits.

    Set proper permissions (contains private keys!):

    # Create the file if it doesn't exist
    touch ./letsencrypt/acme.json
    
    # Set restrictive permissions
    chmod 600 ./letsencrypt/acme.json

    Challenge Types

    Let's Encrypt needs to verify you control the domain. Traefik supports three methods:

    ChallengeHow It WorksWhen to Use
    HTTP-01Requests /.well-known/acme-challenge/ on port 80Most common, simple setup
    TLS-ALPN-01Verification over TLS on port 443When port 80 is blocked
    DNS-01Create a DNS TXT recordWildcards, internal servers

    DNS Challenge for Wildcards

    certificatesResolvers:
      letsencrypt:
        acme:
          email: admin@example.com
          storage: /letsencrypt/acme.json
          dnsChallenge:
            provider: cloudflare
            resolvers:
              - "1.1.1.1:53"
              - "8.8.8.8:53"

    DNS providers require API credentials via environment variables:

    Docker Compose example with Cloudflare:

    services:
      traefik:
        environment:
          - CF_API_EMAIL=your-cloudflare-email@example.com
          - CF_API_KEY=your-cloudflare-global-api-key
          # Or use scoped API token:
          # - CF_DNS_API_TOKEN=your-cloudflare-dns-token

    Each DNS provider has different env vars. See Traefik's DNS provider documentation for your provider's requirements.

    HTTP to HTTPS Redirect

    Force all traffic to HTTPS:

    Global redirect (static config):

    entryPoints:
      web:
        address: ":80"
        http:
          redirections:
            entryPoint:
              to: websecure
              scheme: https
      websecure:
        address: ":443"

    Per-service redirect (Docker labels):

    labels:
      - "traefik.http.routers.myapp-http.rule=Host(`app.example.com`)"
      - "traefik.http.routers.myapp-http.entrypoints=web"
      - "traefik.http.routers.myapp-http.middlewares=redirect-to-https"
      - "traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https"
      - "traefik.http.middlewares.redirect-to-https.redirectscheme.permanent=true"

    3. Manual Certificate Configuration

    When you can't use Let's Encrypt—internal services, enterprise PKI, or wildcard certificates from commercial CAs.

    File-Based Certificates

    Dynamic configuration (certs.yml):

    tls:
      certificates:
        - certFile: /certs/example.com.crt
          keyFile: /certs/example.com.key
        - certFile: /certs/wildcard.example.com.crt
          keyFile: /certs/wildcard.example.com.key

    Static configuration to load dynamic config:

    providers:
      file:
        filename: /etc/traefik/certs.yml
        watch: true

    The watch: true setting means Traefik reloads certificates when the file changes—no restart required.

    Docker Compose with Manual Certificates

    version: "3.9"
    
    services:
      traefik:
        image: traefik:v3.0
        command:
          - "--providers.docker=true"
          - "--providers.file.filename=/etc/traefik/dynamic.yml"
          - "--providers.file.watch=true"
          - "--entrypoints.web.address=:80"
          - "--entrypoints.websecure.address=:443"
        ports:
          - "80:80"
          - "443:443"
        volumes:
          - "/var/run/docker.sock:/var/run/docker.sock:ro"
          - "./certs:/certs:ro"
          - "./dynamic.yml:/etc/traefik/dynamic.yml:ro"
    
      myapp:
        image: nginx:alpine
        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"

    Note: With manual certificates, use tls=true instead of tls.certresolver.

    Default Certificate

    Set a fallback certificate for requests without SNI or unmatched domains:

    tls:
      stores:
        default:
          defaultCertificate:
            certFile: /certs/default.crt
            keyFile: /certs/default.key

    4. Kubernetes TLS Configuration

    Traefik integrates with Kubernetes via IngressRoute CRDs or standard Ingress resources.

    IngressRoute with Let's Encrypt

    Traefik Helm values (values.yaml):

    additionalArguments:
      - "--certificatesresolvers.letsencrypt.acme.email=admin@example.com"
      - "--certificatesresolvers.letsencrypt.acme.storage=/data/acme.json"
      - "--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web"
    
    persistence:
      enabled: true
      size: 128Mi

    IngressRoute:

    apiVersion: traefik.io/v1alpha1
    kind: IngressRoute
    metadata:
      name: myapp
      namespace: default
    spec:
      entryPoints:
        - websecure
      routes:
        - match: Host(`app.example.com`)
          kind: Rule
          services:
            - name: myapp
              port: 80
      tls:
        certResolver: letsencrypt

    IngressRoute with Kubernetes Secrets

    Use certificates stored as Kubernetes Secrets:

    Create the secret:

    kubectl create secret tls myapp-tls \
      --cert=app.example.com.crt \
      --key=app.example.com.key

    IngressRoute:

    apiVersion: traefik.io/v1alpha1
    kind: IngressRoute
    metadata:
      name: myapp
    spec:
      entryPoints:
        - websecure
      routes:
        - match: Host(`app.example.com`)
          kind: Rule
          services:
            - name: myapp
              port: 80
      tls:
        secretName: myapp-tls

    Standard Kubernetes Ingress

    apiVersion: networking.k8s.io/v1
    kind: Ingress
    metadata:
      name: myapp
      annotations:
        traefik.ingress.kubernetes.io/router.entrypoints: websecure
        traefik.ingress.kubernetes.io/router.tls: "true"
    spec:
      tls:
        - hosts:
            - app.example.com
          secretName: myapp-tls
      rules:
        - host: app.example.com
          http:
            paths:
              - path: /
                pathType: Prefix
                backend:
                  service:
                    name: myapp
                    port:
                      number: 80

    5. TLS Options and Security

    Configure TLS versions, cipher suites, and other security settings.

    Minimum TLS Version

    tls:
      options:
        default:
          minVersion: VersionTLS12
        
        strict:
          minVersion: VersionTLS13

    Apply options to a router:

    # Docker label
    - "traefik.http.routers.myapp.tls.options=strict@file"
    
    # IngressRoute
    tls:
      options:
        name: strict

    Custom Cipher Suites

    tls:
      options:
        modern:
          minVersion: VersionTLS12
          cipherSuites:
            - TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
            - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
            - TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305
            - TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305

    HSTS Headers

    http:
      middlewares:
        hsts:
          headers:
            stsSeconds: 31536000
            stsIncludeSubdomains: true
            stsPreload: true

    Apply to router:

    - "traefik.http.routers.myapp.middlewares=hsts@file"

    → See our HSTS guide for more on Strict Transport Security.

    6. Certificate Renewal and Monitoring

    Automatic Renewal

    Let's Encrypt certificates are valid for 90 days. Traefik automatically renews them 30 days before expiration. No action required—just ensure:

    1. The acme.json file persists across container restarts
    2. The ACME challenge endpoint remains accessible
    3. Your email is valid for expiration warnings

    Checking Certificate Status

    Via command line:

    # Check certificate from Traefik
    openssl s_client -connect app.example.com:443 -servername app.example.com </dev/null 2>/dev/null | openssl x509 -noout -dates
    
    # Check acme.json contents (careful—contains private keys!)
    cat acme.json | jq '.letsencrypt.Certificates[].domain'

    Rate Limit Awareness

    Let's Encrypt has rate limits:

    • 50 certificates per domain per week
    • 5 duplicate certificates per week
    • 5 failed validations per hour

    For testing, use the staging environment:

    certificatesResolvers:
      letsencrypt:
        acme:
          caServer: https://acme-staging-v02.api.letsencrypt.org/directory
          # ... rest of config

    Staging certificates aren't trusted by browsers but don't count against rate limits.

    7. Troubleshooting

    Certificate Not Loading

    Symptoms: Browser shows default Traefik certificate or "connection refused"

    Check:

    1. Router has tls=true or tls.certresolver set
    2. Certificate files exist and are readable
    3. Certificate matches the requested hostname
    4. No syntax errors in dynamic config
    # Verify Traefik can read cert files
    docker exec traefik cat /certs/example.com.crt | openssl x509 -noout -subject
    
    # Check Traefik logs
    docker logs traefik 2>&1 | grep -i "certificate\|tls\|error"

    ACME Challenge Failing

    Symptoms: Let's Encrypt returns "unauthorized" or "connection refused"

    IssueSolution
    Port 80 blockedEnsure firewall/cloud security allows port 80 inbound
    Wrong entrypointHTTP challenge needs web entrypoint (port 80)
    DNS not pointingVerify domain resolves to Traefik's IP
    Cloudflare proxyUse "Full (strict)" SSL mode or DNS challenge

    Debug ACME:

    # Enable ACME debug logging
    log:
      level: DEBUG
    
    # Test HTTP challenge manually
    curl -v http://app.example.com/.well-known/acme-challenge/test

    Chain Issues

    Symptoms: Certificate works in browser but fails in curl/API clients

    Solution - ensure your certificate file includes the full chain:

    # Certificate file should contain (in order):
    # 1. Your certificate
    # 2. Intermediate certificate(s)
    # 3. (Optional) Root certificate
    
    cat server.crt intermediate.crt > fullchain.crt

    → See our Certificate Chain guide for more on chain ordering.

    503 Service Unavailable

    Symptoms: HTTPS works but returns 503 error

    CauseSolution
    Service not foundVerify traefik.http.services.*.loadbalancer.server.port is set
    Backend not runningCheck container health with docker ps
    Network isolationEnsure Traefik and backend share a Docker network

    Mixed Content Warnings

    Symptoms: Page loads but shows security warnings, some assets blocked

    This happens when your HTML references HTTP resources on an HTTPS page. Fix by:

    • Using protocol-relative URLs (//cdn.example.com/asset.js)
    • Forcing HTTPS URLs in your application code
    • Adding the upgrade-insecure-requests CSP header

    8. Best Practices

    Production Checklist

    PracticeWhy
    Persist acme.jsonLosing it means re-requesting all certificates
    Set proper file permissionschmod 600 acme.json - contains private keys
    Use DNS challenge for wildcardsHTTP challenge can't validate wildcards
    Monitor certificate expirationEven with auto-renewal, things can fail
    Test with staging firstAvoid rate limits during development
    Enable HSTSPrevent downgrade attacks
    Disable TLS 1.0/1.1Use TLS 1.2+ minimum

    Security Configuration

    tls:
      options:
        default:
          minVersion: VersionTLS12
          sniStrict: true
    
    http:
      middlewares:
        security-headers:
          headers:
            stsSeconds: 31536000
            stsIncludeSubdomains: true
            contentTypeNosniff: true
            browserXssFilter: true

    High Availability Considerations

    For multi-instance Traefik deployments:

    • Use distributed ACME storage (Consul, etcd, Redis)
    • Or use a single Traefik instance for certificate management
    • Consider cert-manager in Kubernetes environments
    certificatesResolvers:
      letsencrypt:
        acme:
          storage: consul://localhost:8500/traefik/acme/account

    9. Frequently Asked Questions

    Quick Reference

    Docker Labels Cheat Sheet

    # Enable Traefik for container
    - "traefik.enable=true"
    
    # Basic HTTPS router
    - "traefik.http.routers.myapp.rule=Host(`app.example.com`)"
    - "traefik.http.routers.myapp.entrypoints=websecure"
    - "traefik.http.routers.myapp.tls.certresolver=letsencrypt"
    
    # Manual certificate (from file)
    - "traefik.http.routers.myapp.tls=true"
    
    # HTTP to HTTPS redirect
    - "traefik.http.routers.myapp-http.rule=Host(`app.example.com`)"
    - "traefik.http.routers.myapp-http.entrypoints=web"
    - "traefik.http.routers.myapp-http.middlewares=redirect-https"
    - "traefik.http.middlewares.redirect-https.redirectscheme.scheme=https"
    
    # Specify service port (if not default)
    - "traefik.http.services.myapp.loadbalancer.server.port=8080"
    
    # TLS options
    - "traefik.http.routers.myapp.tls.options=modern@file"

    Minimal Let's Encrypt Setup

    # traefik.yml
    entryPoints:
      web:
        address: ":80"
        http:
          redirections:
            entryPoint:
              to: websecure
      websecure:
        address: ":443"
    
    certificatesResolvers:
      letsencrypt:
        acme:
          email: admin@example.com
          storage: /letsencrypt/acme.json
          httpChallenge:
            entryPoint: web
    
    providers:
      docker:
        exposedByDefault: false