Custom TLS configuration for Traefik

This post teach you how to configure Traefik with a custom TLS configuration.

This post tends to give you some custom configuration files to work with Traefik and TLS.

Goal: Configure Traefik with custom TLS settings

Environment: Debian 13, Docker 28.x, docker compose (plugin) 2.32.x, Traefik 3.6.

Execution context:

bash
jho@vmi866042:/opt/docker/dc$ tree
.
├── conf
│   ├── acme.json
│   ├── traefik.yml
│   ├── traefikdynamic
│   │   ├── dynamic.yml
├── docker-compose.yml
└── logs
    ├── traefikAccess.log
    ├── traefik.log

Result of the “tree” command in the /opt/docker/dc folder

  • path where are every folder and files : /opt/docker/dc
  • path of the principal configuration file for Traefik : /opt/docker/dc/conf/traefik.yml
  • folder where are every dynamic configuration files : /opt/docker/dc/conf/traefikdynamic
  • path of the file which is used to store SSL certificates for let’s encrypt (or other provider) : /opt/docker/dc/conf/acme.json
  • folder to store logs : /opt/docker/dc/logs/

docker-compose configuration file#

Forward, you can’t use the two types of configuration (static and dynamic) at the same time, it is only once. I prefer dynamic configuration, here’s a quick reminder .

Let’s prepare the docker-compose.yml file:

bash
---
services:
  traefik:
    image: traefik:v3.6
    container_name: traefik
    restart: unless-stopped
    ports:
      - target : 80
        published : 80
        protocol: tcp
        mode : host
      ### BEGIN dashboad
      - target : 8080
        published : 8080
        protocol: tcp
        mode : host
      ### END dashboard
      - target : 443
        published : 443
        protocol: tcp
        mode : host
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./conf/traefikdynamic:/dynamic
      - ./conf/traefik.yml:/etc/traefik/traefik.yml:ro
      - ./conf/acme.json:/etc/traefik/acme.json
      - ./logs/traefik.log:/etc/traefik/applog.log
    environment:
      TZ: Europe/Paris

Traefik configuration#

Every configuration files here are stored in the “conf” folder. You can change the path to where you want, but remember to update your docker-compose file for it.

Content of the file traefik.yml :

bash
---
global:
  sendAnonymousUsage: false
  checkNewVersion: false

api:
  dashboard: true

log:
  filePath: "/etc/traefik/applog.log"
  format: json
  level: "ERROR"

providers:
  docker:
    endpoint: unix:///var/run/docker.sock
    exposedByDefault: false
    watch: true
  file:
    directory: "/dynamic"
    watch: true

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

certificatesResolvers:
  letsencrypt:
    acme:
      email: contact@domain.local
      caServer: https://acme-staging-v02.api.letsencrypt.org/directory
#      caServer: https://acme-v02.api.letsencrypt.org/directory
      storage: "/etc/traefik/acme.json"
      keyType: EC256
      httpChallenge:
        entryPoint: web

In this file, one important bloc is “provider” with the definition of a “dynamic” folder path. This folder is created locally to have the file “dynamic.yml”.

The line watch: true allows Traefik to watch changes in the configuration files and applies them when an update is done.

Certificates are generated with Let’s Encrypt ; indeed, Traefik uses its internal tool “lego”. When you are in build mode, you should use “staging” server, which have a less aggressive limiter than the “acme-v02”. In production environment, use the “acme-v02” server from Let’s Encrypt.

Dynamic configuration file is the same type as the main configuration file (YAML), difference is the settings. Here’s my dynamic configuration file dynamic.yml, in the folder conf/traefikdynamic :

bash
---
tls:
  options:
    default:
      minVersion: VersionTLS12
      sniStrict: true
      cipherSuites:
        - TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
        - TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
        - TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305
        - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
        - TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305
        - TLS_AES_128_GCM_SHA256
        - TLS_AES_256_GCM_SHA384
        - TLS_CHACHA20_POLY1305_SHA256
      curvePreferences:
        - CurveP521
        - CurveP384
      alpnProtocols:
        - h2
        - http/1.1
    mintls13:
      minVersion: VersionTLS13

http:
  middlewares:
    security:
      headers:
        accessControlAllowMethods:
          - GET
          - OPTIONS
          - PUT
        accessControlMaxAge: 100
        addVaryHeader: true
        contentTypeNosniff: true
        customFrameOptionsValue: SAMEORIGIN
        customResponseHeaders:
          Access-Control-Allow-Origin: "*"
          Sec-Fetch-Site: cross-site
          X-Forwarded-Proto: https
        forceSTSHeader: true
        frameDeny: true
        referrerPolicy: "strict-origin-when-cross-origin"
        stsIncludeSubdomains: true
        stsPreload: true
        stsSeconds: 31536000

Some explanations :

  • TLS block specify every needed options to have an optimal configuration. Minimal version of TLS is 1.2 with newer algorithms used by modern navigators ;
  • HTTP bloc have 2 “under-blocs” :
  • a middleware for every security setting about HTTPS (headers)

Docker labels#

Configuration files are ready, you now have to set up your services in the docker-compose.yml file. Here’s what you need for every services behind Traefik :

bash
services:
  portainer:
...
    labels:
      traefik.enable: "true"
      traefik.http.routers.service-https.entrypoints: "websecure"
      traefik.http.routers.service-https.middlewares: "security@file"
      traefik.http.routers.service-https.tls: "true"
      traefik.http.routers.service-https.tls.certresolver: "letsencrypt"

Middleware security@file is defined in the file dynamic.yml.

When you set the docker labels, don’t forget to re-up your containers (docker-compose up -d).

Don’t want to use docker labels?#

If you don’t use docker labels, you can add these blocs in the “traefikdynamic.yml” – information about “routers” and “services” next to the TLS configuration, in the bloc http : (for example, I’m using Portainer)

yaml
  services:
    sc-portainer:
      loadBalancer:
        servers:
        - url: "http://portainer:9000"

   routers:
     rt-portainer:
      entryPoints:
      - websecure
      middlewares:
      - security
      service: sc-portainer
      rule: Host(`portainer.domain.local`)
      tls:
        certResolver: letsencrypt

The “services” block defines the containers to be accessed within your infrastructure, along with the communication port. Each service must be defined to be operated through Traefik, as shown in the example above.

The “routers” block defines the route and middlewares for the services. In this case, I want the “sc-portainer” service to use both middlewares, making it accessible via “portainer.domain.local” (line rule:), and reachable via HTTPS from the outside (certificate generated using the letsencrypt service defined in the traefik.yml file).

This configuration is more convenient as it doesn’t require changing your service’s docker-compose.yml file. Traefik applies the changes on-the-fly from the dynamic configuration files.

Stay Updated

Subscribe to the RSS feed or follow for new articles.

Related articles

No image
#traefik

Traefik, a service to rules them all

Traefik is an open-source reverse-proxy and load-balancer HTTP, TCP and UDP. This article give you some information about it.

Read more

Latest in #traefik