🛡️ Traefik 2, custom TLS configuration

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

logo traefik

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

Version Date Comment
1 05/2022 Post creation
2 08/2023 Change paths, add environment and context, update versions docker-compose file, remove "compress" middleware, add headers in the post to show parts

Goal : Configure Traefik with custom TLS settings

Environment : Debian 12, Docker 24.x, docker compose (plugin) 2.20.x, Traefik 2.10.

Execution context :

jho@vmi866042:/opt/docker/dc$ tree
.
├── conf
│   ├── acme.json
│   ├── traefik.yml
│   ├── traefikdynamic
│   │   ├── general.yml
│   │   ├── routersservices.yml
├── docker-compose.yml
└── logs
    ├── traefikAccess.log
    ├── traefik.log
  • 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:

---
services:
  traefik:
    image: traefik:saintmarcelin
    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 :

---
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
    swarmMode: false
  file:
    directory: "/dynamic"
    watch: true

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

certificatesResolvers:
  letsencrypt:
    acme:
      email: [email protected]
      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 “traefik_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 “traefik_dynamic.yml”, in the folder “conf/traefikdynamic” :

---
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_128_CBC_SHA256
        - 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
        browserXssFilter: true
        contentTypeNosniff: true
        customFrameOptionsValue: SAMEORIGIN
        customResponseHeaders:
          Access-Control-Allow-Origin: "*"
          Sec-Fetch-Site: cross-site
          X-Forwarded-Proto: https
        forceSTSHeader: true
        frameDeny: true
        hostsProxyHeaders:
          - "X-Forwarded-Host"
        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 to compress some flux aside streaming
    - a middleware for every security 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 :

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"

Middlewares security@file and compression@file are drawn in the file traefik_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)

  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.