Force minimum TLS version using CLI or labels

Been trying to force to set the minimum TLS version in Traefik using labels or CLI option on docker-compose.
There is a post from September that says it's not possible:

But then I find a 2 years old (solved) issue:
Ability to set ciphersuites and MinTLSVersion by CLI · Issue #3103 · traefik/traefik · GitHub and its PR Support TLS MinVersion and CipherSuite as CLI option. by ldez · Pull Request #3107 · traefik/traefik · GitHub

My config:

version: "3.8"
services:
  traefik:
    image: "traefik:v2.3.4"
    container_name: "traefik"
    command:
      - "--log.level=DEBUG"
      - "--api.insecure=true"
      - "--providers.docker=true"
      - "--providers.docker.exposedbydefault=false"
      - "--entrypoints.websecure.address=:443"
      - "--certificatesresolvers.route53.acme.dnschallenge=true"
      - "--certificatesresolvers.route53.acme.dnschallenge.provider=route53"
      - "--certificatesresolvers.route53.acme.email=${EMAIL}"
      - "--certificatesresolvers.route53.acme.storage=/letsencrypt/acme.json" 
    ports:
      - "443:443"
    volumes:
      - "./letsencrypt:/letsencrypt"
      - "/var/run/docker.sock:/var/run/docker.sock:ro"
    environment:
      - AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID}
      - AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY}
      - AWS_HOSTED_ZONE_ID=${AWS_HOSTED_ZONE_ID}
    labels:
      traefik.enable: true
      traefik.http.routers.traefik.rule: Host(`traefik.${DOMAIN}`)
      traefik.http.routers.traefik.tls.certresolver: route53
      traefik.http.routers.traefik.entrypoints: websecure
      traefik.http.services.traefik.loadbalancer.server.port: 8080
[...]

Tried:

- "--entrypoints.websecure.address=:443 TLS TLS.MinVersion:VersionTLS11"
- "--entrypoints.websecure.http.tls.options.default.MinVersion=VersionTLS13"
- "--entrypoints='Name:websecure TLS TLS:MinVersion:VersionTLS13'"

Testing was done with testssl.sh.

Hello, I do have a solution for you. I had similar issues for setting up TLS. See my site for a full breakdown. Hope it helps.

https://www.djpic.net/articles/traefik-v2-secure-tls-and-header-configuration-with-docker-provider/

I've been working with Traefik for a few weeks, tinkering here and there trying to get it all working how I want it. The only piece I can't get to work at all is the minimum TLS options in Docker Compose. I've looked over your examples @webmastadj but I can't seem to get it. Clearly something wrong with my configuration elsewhere.

Hopefully someone will be able to point out where I've made a rookie mistake.

docker-compose.yml

version: "3.7"

networks:
  default:
    driver: bridge
  socket_proxy:
    name: socket_proxy
    driver: bridge
  traefik_proxy:
    name: traefik_proxy
    driver: bridge

services:
  # Including socket-proxy as it is a dependency in this example.
  socket-proxy:
    image: docker.io/tecnativa/docker-socket-proxy:latest
    container_name: socket-proxy
    restart: unless-stopped
    privileged: true
    ports:
      - "127.0.0.1:2375:2375/tcp"
    networks:
      - socket_proxy
    environment:
      LOG_LEVEL: info
      EVENTS: 1
      PING: 1
      VERSION: 1
      AUTH: 0
      SECRETS: 0
      POST: 0
      BUILD: 0
      COMMIT: 0
      CONFIGS: 0
      CONTAINERS: 1
      DISTRIBUTION: 0
      EXEC: 0
      IMAGES: 1
      INFO: 0
      NETWORKS: 0
      NODES: 0
      PLUGINS: 0
      SERVICES: 0
      SESSION: 0
      SWARM: 0
      SYSTEM: 0
      TASKS: 0
      VOLUMES: 0
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock

  reverse-proxy:
    build:
      context: ./containers/traefik
      dockerfile: Dockerfile
    image: docker.io/custom-traefik:2.5.1
    container_name: traefik
    ports:
      - "80:80/tcp"
      - "443:443/tcp"
    networks:
      - traefik_proxy
      - socket_proxy
    environment:
      CLOUDFLARE_EMAIL: ${CLOUDFLARE_EMAIL}
      CLOUDFLARE_DNS_API_TOKEN: ${CLOUDFLARE_DNS_API_TOKEN}
      CLOUDFLARE_ZONE_API_TOKEN: ${CLOUDFLARE_ZONE_API_TOKEN}
      TRAEFIK_ACCESSLOG: "true"
      TRAEFIK_ACCESSLOG_BUFFERINGSIZE: 100
      TRAEFIK_ACCESSLOG_FILEPATH: /access.log
      TRAEFIK_ACCESSLOG_FILTERS_STATUSCODES: 400-499
      TRAEFIK_ACCESSLOG_FORMAT: common
      TRAEFIK_API: "true"
      TRAEFIK_API_DASHBOARD: "true"
      TRAEFIK_API_DEBUG: "true"
      TRAEFIK_API_INSECURE: "false"
      TRAEFIK_CERTIFICATESRESOLVERS_CLOUDFLARE: "true"
      TRAEFIK_CERTIFICATESRESOLVERS_CLOUDFLARE_ACME_CASERVER: ${TRAEFIK_CASERVER}
      TRAEFIK_CERTIFICATESRESOLVERS_CLOUDFLARE_ACME_EMAIL: ${TRAEFIK_EMAIL}
      TRAEFIK_CERTIFICATESRESOLVERS_CLOUDFLARE_ACME_KEYTYPE: RSA4096
      TRAEFIK_CERTIFICATESRESOLVERS_CLOUDFLARE_ACME_STORAGE: /acme.json
      TRAEFIK_CERTIFICATESRESOLVERS_CLOUDFLARE_ACME_DNSCHALLENGE: "true"
      TRAEFIK_CERTIFICATESRESOLVERS_CLOUDFLARE_ACME_DNSCHALLENGE_DELAYBEFORECHECK: 0
      TRAEFIK_CERTIFICATESRESOLVERS_CLOUDFLARE_ACME_DNSCHALLENGE_PROVIDER: cloudflare
      TRAEFIK_CERTIFICATESRESOLVERS_CLOUDFLARE_ACME_DNSCHALLENGE_RESOLVERS: 1.1.1.1:53,1.0.0.1:53
      TRAEFIK_ENTRYPOINTS_HTTP_ADDRESS: ":80"
      TRAEFIK_ENTRYPOINTS_HTTPS_ADDRESS: ":443"
      TRAEFIK_ENTRYPOINTS_HTTPS_HTTP_TLS: "true"
      TRAEFIK_ENTRYPOINTS_HTTPS_HTTP_TLS_CERTRESOLVER: cloudflare
      TRAEFIK_ENTRYPOINTS_HTTPS_HTTP_TLS_DOMAINS_0_MAIN: ${BASE_DOMAIN}
      TRAEFIK_ENTRYPOINTS_HTTPS_HTTP_TLS_DOMAINS_0_SANS: "*.${BASE_DOMAIN}"
      TRAEFIK_GLOBAL_CHECKNEWVERSION: "false"
      TRAEFIK_GLOBAL_SENDANONYMOUSUSAGE: "false"
      TRAEFIK_LOG: "true"
      TRAEFIK_LOG_FORMAT: common
      TRAEFIK_LOG_LEVEL: DEBUG
      TRAEFIK_PILOT_DASHBOARD: "false"
      TRAEFIK_PING: "true"
      TRAEFIK_PING_MANUALROUTING: "true"
      TRAEFIK_PROVIDERS_DOCKER: "true"
      TRAEFIK_PROVIDERS_DOCKER_ENDPOINT: tcp://socket-proxy:2375
      TRAEFIK_PROVIDERS_DOCKER_EXPOSEDBYDEFAULT: "false"
      TRAEFIK_PROVIDERS_DOCKER_NETWORK: traefik_proxy
      TRAEFIK_PROVIDERS_DOCKER_SWARMMODE: "false"
      TRAEFIK_PROVIDERS_DOCKER_WATCH: "true"
      TRAEFIK_PROVIDERS_FILE_DEBUGLOGGENERATEDTEMPLATE: "false"
      TRAEFIK_PROVIDERS_FILE_DIRECTORY: /config
      TRAEFIK_PROVIDERS_FILE_WATCH: "true"
    labels:
      traefik.enable: "true"
      traefik.docker.network: traefik_proxy
      # Redirect
      traefik.http.routers.http-catch-all.entrypoints: http
      traefik.http.routers.http-catch-all.rule: HostRegexp(`{host:.+}`)
      traefik.http.routers.http-catch-all.middlewares: https-redirect@file
      traefik.http.routers.http-catch-all.service: reverse-proxy@docker
      traefik.http.services.reverse-proxy.loadbalancer.server.port: 80
      # Routers
      traefik.http.routers.api.entrypoints: https
      traefik.http.routers.api.middlewares: basic-auth@docker
      traefik.http.routers.api.rule: Host(`traefik.${BASE_DOMAIN}`) && (PathPrefix(`/api`) || PathPrefix(`/dashboard`))
      traefik.http.routers.api.service: api@internal
      traefik.http.routers.api.tls.certresolver: cloudflare
      traefik.http.routers.ping.entrypoints: https
      traefik.http.routers.ping.rule: Host(`traefik.${BASE_DOMAIN}`) && PathPrefix(`/ping`)
      traefik.http.routers.ping.service: ping@internal
      traefik.http.routers.ping.tls.certresolver: cloudflare
      # Middleware
      traefik.http.middlewares.basic-auth.basicauth.users: ${TRAEFIK_DASHBOARD_ADMIN}
    volumes:
      - ./config/traefik/file:/config
      - ./log/traefik/access.log:/access.log
    depends_on:
      - socket-proxy

I'm on Windows, so the Traefik image is a minor customization of the default to add an acme.json

Dockerfile

FROM docker.io/traefik:2.5.1
RUN touch /acme.json && chmod 0600 /acme.json

And finally my File Provider configurations

http:
  middlewares:
    # HTTPS Scheme redirect
    https-redirect:
      redirectScheme:
        scheme: https

    # Enabled Secure headers
    secure-headers:
      headers:
        sslRedirect: true
        frameDeny: true
        stsIncludeSubdomains: true
        stsPreload: true
        stsSeconds: 63072000
        contentTypeNosniff: true
        accessControlAllowMethods:
          - GET
          - POST
        accessControlMaxAge: 100
        addVaryheader: true
        contentSecurityPolicy: script-src 'self'
        referrerPolicy: origin-when-cross-origin

    # Semi Secure Headers to allow custom contentSecurityPolicy
    semi-secure-headers:
      headers:
        sslRedirect: true
        frameDeny: true
        stsIncludeSubdomains: true
        stsPreload: true
        stsSeconds: 63072000
        contentTypeNosniff: true
        accessControlAllowMethods:
          - GET
          - POST
        accessControlMaxAge: 100
        addVaryheader: true
        referrerPolicy: origin-when-cross-origin

    # Allow compressed content
    compress-content:
      compress: {}

tls:
  options:
    default:
      minVersion: VersionTLS12
      cipherSuites:
        # Recommended ciphers for TLSv1.2
        - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
        - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
        - TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305
        # Recommended ciphers for TLSv1.3
        - TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
        - TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
        - TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305

    tlsv13only:
      minVersion: VersionTLS13

I know that the File Provider is being consumed, as the extra middleware defined within is showing in the Dashboard. No matter how I reference the TLS options, however, I'm still getting results back from sslyze showing

 * TLS 1.1 Cipher Suites:
     Attempted to connect using 80 cipher suites.

     The server accepted the following 2 cipher suites:
        TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA              256       ECDH: prime256v1 (256 bits)
        TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA              128       ECDH: prime256v1 (256 bits)

     The group of cipher suites supported by the server has the following properties:
       Forward Secrecy                    OK - Supported
       Legacy RC4 Algorithm               OK - Not Supported


 * TLS 1.0 Cipher Suites:
     Attempted to connect using 80 cipher suites.

     The server accepted the following 2 cipher suites:
        TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA              256       ECDH: prime256v1 (256 bits)
        TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA              128       ECDH: prime256v1 (256 bits)

     The group of cipher suites supported by the server has the following properties:
       Forward Secrecy                    OK - Supported
       Legacy RC4 Algorithm               OK - Not Supported

I'm assuming that this may be due to the horrific things I've done to the Traefik routers and entrypoints, but I'm really not sure.

Disregard my previous message, it's all working fine.

I'm using Cloudflare to front my DNS, and it is proxying my server. As Cloudflare wasn't configured for minimum TLS of 1.2 it was returning the ciphers mentioned above. I enabled that setting in Cloudflare and it was al lfine. I then disabled the DNS proxy settings to confirm that my local configuration in Traefik was working, and it was.