# Traefik redirect www to non-www for every host in a single dynamic config

I want to define generic middleware to redirect www to non-www for every host, for both http and https. I got it working with labels, but can't make reusable dynamic configuration for middleware and router that will apply redirect to every host, without need to repeat labels in every docker-compose.yml

Here is working docker-compose.yml with labels:

version: '3.9'

services:
  nmc-nginx-with-volume:
    image: nginx:stable-alpine3.17-slim
    container_name: nmc-nginx-with-volume
    restart: unless-stopped
    volumes:
      - ./website:/usr/share/nginx/html
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf
    networks:
      - proxy
    labels:
      # Main
      - 'traefik.enable=true'
      - 'traefik.docker.network=proxy'

      # Main router
      - 'traefik.http.routers.nmc-nginx-with-volume.rule=Host(`${SITE_HOSTNAME}`)'
      - 'traefik.http.routers.nmc-nginx-with-volume.entrypoints=websecure'
      - 'traefik.http.routers.nmc-nginx-with-volume.service=nmc-nginx-with-volume'
      - 'traefik.http.services.nmc-nginx-with-volume.loadbalancer.server.port=8080'

      # Redirect router
      - 'traefik.http.routers.redirect-www.rule=Host(`www.${SITE_HOSTNAME}`)'
      - 'traefik.http.routers.redirect-www.entrypoints=websecure'
      - 'traefik.http.routers.redirect-www.middlewares=redirect-to-non-www'
      - 'traefik.http.routers.redirect-www.service=noop@internal'

      # Middleware to redirect to non-www
      - 'traefik.http.middlewares.redirect-to-non-www.redirectregex.regex=^https://www\\.(.+)'
      - 'traefik.http.middlewares.redirect-to-non-www.redirectregex.replacement=https://$$\\1'
      - 'traefik.http.middlewares.redirect-to-non-www.redirectregex.permanent=true'

networks:
  proxy:
    external: true

And here are my static and dynamic config that fail, when I navigate to www it gets stuck trying to get certificate without ever redirecting to non-www.

Static configuration:

# static configuration
# core/traefik-data/traefik.yml

api:
  dashboard: true

entryPoints:
  web:
    address: :80
    http:
      redirections:
        entryPoint:
          to: websecure

  websecure:
    address: :443
    http:
      middlewares:
        - secureHeaders@file
      tls:
        certResolver: letsencrypt

providers:
  docker:
    endpoint: 'unix:///var/run/docker.sock'
    exposedByDefault: false
  file:
    # filename: /configurations/dynamic.yml
    # with www redirect
    filename: /configurations/dynamic-www-redirect.yml

certificatesResolvers:
  letsencrypt:
    acme:
      # email moved to docker-compose command: for env var
      # email: changeme@changeme.org

      # always start with staging certificate
      # caServer: "https://acme-staging-v02.api.letsencrypt.org/directory"
      caServer: 'https://acme-v02.api.letsencrypt.org/directory'
      
      storage: acme.json
      keyType: EC384
      httpChallenge:
        entryPoint: web

Dynamic configuration:

# dynamic configuration
# core/traefik-data/configurations/dynamic-www-redirect.yml

http:
  middlewares:
    secureHeaders:
      headers:
        sslRedirect: true
        forceSTSHeader: true
        stsIncludeSubdomains: true
        stsPreload: true
        stsSeconds: 31536000

    user-auth:
      basicAuth:
        users:
          - '{{ env "TRAEFIK_AUTH" }}'

    redirect-to-non-www:
      redirectRegex:
        regex: "^https?://www\\.(.+)"
        replacement: "https://${1}"
        permanent: true

  routers:
    redirect-www-http:
      rule: "HostRegexp(`www.{domain:.+}`)"
      entryPoints:
        - web
      middlewares:
        - redirect-to-non-www
      service: noop@internal

    redirect-www-https:
      rule: "HostRegexp(`www.{domain:.+}`)"
      entryPoints:
        - websecure
      middlewares:
        - redirect-to-non-www
      tls:
        # you cant redirect https://www to https://non-www without resolving certificate
        certResolver: letsencrypt
      service: noop@internal

tls:
  options:
    default:
      cipherSuites:
        - TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
        - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
        - TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
        - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
        - TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305
        - TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305
      minVersion: VersionTLS12

How to get www to non-www redirect for every host, for both http and https working with generic dynamic configuration located in a single place that will apply to every container? And resolve that missing certificate step?

You can place a http redirect middleware globally on entrypoint. But as dynamic config it needs to be loaded via a provider, either from file (referencing with @file) or from a Docker container (used here for simplicity).

Example:

services:
  traefik:
    image: traefik:latest
    ports:
      - 80:80
      - 443:443
    networks:
      - proxy
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - ./certificates:/certificates
      #- /var/log:/var/log
    command:
      - --api.dashboard=true
      - --log.level=INFO
      #- --log.filepath=/var/log/traefik.log
      - --accesslog=true
      #- --accesslog.filepath=/var/log/traefik-access.log
      - --providers.docker.network=proxy
      - --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
      - --entrypoints.websecure.asDefault=true
      - --entrypoints.websecure.http.tls.certresolver=myresolver
      - --entrypoints.websecure.http.middlewares=mywwwredirect@docker
      - --certificatesresolvers.myresolver.acme.tlschallenge=true
      - --certificatesresolvers.myresolver.acme.storage=/certificates/acme.json
    labels:
      - traefik.enable=true
      - traefik.http.routers.mydashboard.rule=Host(`traefik.example.com`)
      - traefik.http.routers.mydashboard.service=api@internal
      - traefik.http.routers.mydashboard.middlewares=myauth
      - traefik.http.middlewares.myauth.basicauth.users=test:$$apr1$$H6uskkkW$$IgXLP6ewTrSuBkTrqE8wj/

  whoami:
    image: traefik/whoami:v1.10
    hostname: whoami
    networks:
      - proxy
    labels:
      - traefik.enable=true
      - traefik.http.routers.mywhoami.rule=Host(`example.com`) || Host(`www.example.com`)
      - traefik.http.services.mywhoami.loadbalancer.server.port=80

      - traefik.http.middlewares.mywwwredirect.redirectregex.regex=^https://www\.(.*)
      - traefik.http.middlewares.mywwwredirect.redirectregex.replacement=https://$${1}
      #- traefik.http.routers.mywhoami.middlewares=mywwwredirect

networks:
  proxy:
    name: proxy

Note that in this example it first redirects to https, the from www to non-www. The rule should include both domains (with www and without) to enable creation of according TLS certs.