Redirect from non-www to www with traefik 2

Hello Everybody,

I would like to redirect from non-www to www like Google does.

What I would like to do:

Type in the browser:
https://nomeadominio.it/
obtain:
https://www.nomeadominio.it/
Type in the browser:
https://www.nomeadominio.it/
obtain:
https://www.nomeadominio.it/

What I write:

version: "3.9"

services:
  traefik:
    ...
    command:
      ...
      - --entrypoints.websecure.http.middlewares.redirect-non-www-to-www.redirectregex.permanent=true
      - --entrypoints.websecure.http.middlewares.redirect-non-www-to-www.redirectregex.regex="^https?://(?:www\\.)?(.+)"
      - --entrypoints.websecure.http.middlewares.redirect-non-www-to-www.redirectregex.replacement="https://www.${1}"
    ...

  php:
    ...
    labels:
      traefik.enable: 'true'
      traefik.http.routers.training1.rule: Host(`www.*********.ml`)
      traefik.http.routers.training2.middlewares: redirect-non-www-to-www@file
    ...

Source from which I got the code: How to Redirect from Non-WWW to WWW with Traefik | by Benjamin Rancourt | Geek Culture | Medium

What I get:

ERROR: Invalid interpolation format for "command" option in service "traefik": "--entrypoints.websecure.http.middlewares.redirect-non-www-to-www.redirectregex.replacement="https://www.${1}""
ubuntu-22-04-lts@webserver:~/www.*********.tk$

What I think:

I cannot understand if it is a Traefik bug, if I am wrong to translate the code I find on the internet or if with the new versions of Traefik this operation is implemented differently. I do not find exhaustive official documentation regarding this functionality.

Other attempts I've made:

  php:
    ...
    labels:
      traefik.enable: 'true'
      traefik.http.routers.training1.rule: Host(`www.*****.ml`)
      traefik.http.middlewares.redirect-non-www-to-www.redirectregex.permanent: true
      traefik.http.middlewares.redirect-non-www-to-www.redirectregex.regex: "^https?://(?:www\\.)?(.+)"
      traefik.http.middlewares.redirect-non-www-to-www.redirectregex.replacement: "https://www.$${1}"
      traefik.http.routers.training2.middlewares: redirect-non-www-to-www@file
    ...
  php:
    ...
    labels:
      traefik.enable: 'true'
      traefik.http.routers.training.rule: Host(`www.*****.ml`)
      traefik.http.middlewares.redirect-non-www-to-www.redirectregex.permanent: true
      traefik.http.middlewares.redirect-non-www-to-www.redirectregex.regex: "^https?://(?:www\\.)?(.+)"
      traefik.http.middlewares.redirect-non-www-to-www.redirectregex.replacement: "https://www.$${1}"
      traefik.http.routers.training.middlewares: redirect-non-www-to-www@file
    ...
      etc...

The above codes do not give compilation errors but do not work. As I wrote below I am looking for a global solution and not a specific one for each service.

What I don't want:

I don't want to have 2 valid URLs but only the one with www.
I don't want to write a specific implementation for each service but I want the redirect to be valid for all services and write the code only once.

  labels: 
    traefik.enable: 'true'
    traefik.http.routers.training.rule: Host(www.########.ml,########.ml)
    traefik.http.middlewares.test-redirectregex.redirectregex.regex: ^http://########.ml/(.*)
    traefik.http.middlewares.test-redirectregex.redirectregex.replacement: ########.ml/$${1}

This is another solution I've tried but it doesn't work.

The common regex for this is (www\.)?(.*) this matches both www.domain.local and domain.local.

But if you use that unconditionally as a middleware for your router the only thing that will happen is constant redirect.

To redirect on TLS the certificate(s) will have to match both hostnames, this necessitates a Host rule for each or the tls.domains labels and/or a wildcard certificate.

I'm going to use the following requirements:

  1. You want to route particular www.domain.com to a service.
  2. Any unmatched request will get redirected with www. prefixed to the hostname.
  3. TLS for the requests.
  4. Using ALPN-01 for LetsEncrypt Challenge
  5. Using LetsEncrypt Staging server until its working
version: "3"

services:
  traefik:
    image: traefik:latest
    ports:
      - "80:80" 
      - "443:443" 
    command:
      - --api.dashboard=true 
      - --log.level=warning
      - --accesslog.format=json
      - --providers.docker=true 
      - --providers.docker.exposedbydefault=false 
      - --certificatesresolvers.le.acme.email=test@example.com
      - --certificatesresolvers.le.acme.storage=/acme/acme.json
      - --certificatesresolvers.le.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory
      - --entrypoints.web.address=:80 
      - --entrypoints.web.http.tls.certResolver=le
      - --entrypoints.web.http.redirections.entryPoint.to=websecure
      - --entrypoints.web.http.redirections.entryPoint.scheme=https
      - --entrypoints.websecure.address=:443 
      - --entrypoints.websecure.http.tls=true
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock 
      - acme:/acme

    labels:
      traefik.enable: true 
      traefik.http.routers.dashboard.rule: Host(`traefik.localhost`)
      traefik.http.routers.dashboard.service: api@internal
      traefik.http.routers.dashboard.middlewares: auth
      # credentials are test/test
      traefik.http.middlewares.auth.basicauth.users: test:$$apr1$$H6uskkkW$$IgXLP6ewTrSuBkTrqE8wj/

      # low priority rule for otherwise unmatched www. host prefixes
      # without this it is constant redirect
      traefik.http.routers.unmatchedwww.rule: HostRegexp(`{name:^www\..*}`) 
      traefik.http.routers.unmatchedwww.service: noop@internal
      traefik.http.routers.unmatchedwww.priority: 2

      # lowest priority catchall rule, will add/replace www. host portion
      traefik.http.routers.matchlast.rule: PathPrefix(`/`)
      traefik.http.routers.matchlast.priority: 1
      traefik.http.routers.matchlast.middlewares: addwww
      traefik.http.middlewares.addwww.redirectregex.regex: ^https://(?:www\.)?(.*)
      traefik.http.middlewares.addwww.redirectregex.replacement: https://www.$$1

  r1:
    image: traefik/whoami
    labels:
      traefik.enable: true
      traefik.http.routers.r1.rule: Host(`www.whoami.localhost`)
      traefik.http.routers.r1.tls.domains[0].main: whoami.localhost
      traefik.http.routers.r1.tls.domains[0].sans: www.whoami.localhost
      
  r2:
    image: traefik/whoami
    labels:
      traefik.enable: true
      traefik.http.routers.r2.rule: Host(`www.foo.localhost`)
      traefik.http.routers.r2.tls.domains[0].main: foo.localhost
      traefik.http.routers.r2.tls.domains[0].sans: www.foo.localhost
      
  r3:
    image: traefik/whoami
    labels:
      traefik.enable: true
      traefik.http.routers.r3.rule: Host(`www.bar.localhost`)
      traefik.http.routers.r3.tls.domains[0].main: bar.localhost
      traefik.http.routers.r3.tls.domains[0].sans: www.bar.localhost
      
1 Like

Thank you so much! Your script solves all my problems. Your code is perfection. I would not have been able to complete this project even in 100 years of work. A billion thanks! What strength you are! When I saw the project work after thousands of tests, my heart opened.

I'm trying to understand in detail all the lines you added and I have some questions that I report below.

BLOCK 1

    labels:
      traefik.enable: true 
      traefik.http.routers.dashboard.rule: Host(`traefik.*****.ml`)
      traefik.http.routers.dashboard.service: api@internal
      traefik.http.routers.dashboard.middlewares: auth
      traefik.http.middlewares.auth.basicauth.users: test:$$apr1$$H6uskkkW$$IgXLP6ewTrSuBkTrqE8wj/
      traefik.http.routers.unmatchedwww.rule: HostRegexp(`{name:^www\..*}`) 
      traefik.http.routers.unmatchedwww.service: noop@internal
      traefik.http.routers.unmatchedwww.priority: 2
      traefik.http.routers.matchlast.rule: PathPrefix(`/`)
      traefik.http.routers.matchlast.priority: 1
      traefik.http.routers.matchlast.middlewares: addwww
      traefik.http.middlewares.addwww.redirectregex.regex: ^https://(?:www\.)?(.*)
      traefik.http.middlewares.addwww.redirectregex.replacement: https://www.$$1

BLOCK 2

    labels:
      traefik.enable: 'true'
      traefik.http.routers.php.rule: Host(`www.*****.ml`)
      traefik.http.routers.php.tls.domains[0].main: *****.ml
      traefik.http.routers.php.tls.domains[0].sans: www.*****.ml

On block 1 I have some questions, on block 2 no.

Question 1
I understood the following 3 lines perfectly, they are very simple for me too:

      traefik.http.routers.matchlast.middlewares: addwww
      traefik.http.middlewares.addwww.redirectregex.regex: ^https://(?:www\.)?(.*)
      traefik.http.middlewares.addwww.redirectregex.replacement: https://www.$$1

I have only a very small curiosity. In the Traefik documentation that I have read it says that Traefik uses regex 101. If I go to the following address:

I find $$1 wrong, I should use $1 or ${1}, not $$1. If I use $1 or ${1} I get these errors:

ERROR: Invalid interpolation format for "labels" option in service "traefik": https://www.$1

ERROR: Invalid interpolation format for "labels" option in service "traefik": [https://www.${1}](https://www.$%7b1%7d)

It was a mistake that I had already encountered in my past tests. Is the Traefik documentation that writes regex 101 wrong? Is the site I posted wrong or is there some aspect that escapes me?

Question 2

      traefik.enable: true 
      traefik.http.routers.dashboard.rule: Host(`traefik.*****.ml`)
      traefik.http.routers.dashboard.service: api@internal
      traefik.http.routers.dashboard.middlewares: auth
      traefik.http.middlewares.auth.basicauth.users: test:$$apr1$$H6uskkkW$$IgXLP6ewTrSuBkTrqE8wj/

I have read the following page:

but I just don't understand.

  1. I can't even understand the meaning of dynamic configuration and static configuration. A configuration must never change otherwise the project stops working.
  2. I can't understand what this code actually does.
  3. I can't understand why https:\www.traefik. *****.ml is unreachable and if that's right.
  4. I don't understand what the very long code is for after testing and I don't understand if I have to change it and how it is calculated.

In short, on this code I have total darkness.
Question 3

      traefik.http.routers.unmatchedwww.rule: HostRegexp(`{name:^www\..*}`) 
      traefik.http.routers.unmatchedwww.service: noop@internal
      traefik.http.routers.unmatchedwww.priority: 2
      traefik.http.routers.matchlast.rule: PathPrefix(`/`)
      traefik.http.routers.matchlast.priority: 1

I only partially understand this piece of code.
Traefik returns the service only when "www." followed by the domain name because among the specifications in "labels:" there is "Host( www.*****.ml) "so it is necessary to set priorities that allow the "addwww" function to be called to before.
But I don't understand what “PathPrefix”, “api@internal” and “noop@internal” are for and what role they play in the code.

Thanks if you want to answer.

Q1. The documentation suggests the ${} form for referencing the regex groups. It is less error prone than using $1.

If you are testing in regex101 then just one $.

Care should be taken when defining replacement expand variables: $1x is equivalent to ${1x}, not ${1}x (see Regexp.Expand), so use ${1} syntax.

The $$ is from using $ in yaml. If $$ is not used then docker-compose would try and evaluate that variable for interpolation.

If you are testing in regex101 then just one $. When it is written into the Compose file two $.

Composefile reference:

You can use a $ (double-dollar sign) when your configuration needs a literal dollar sign. This also prevents Compose from interpolating a value, so a $ allows you to refer to environment variables that you don’t want processed by Compose.

Q2.1
Staic configuration is responsible for setting up the entrypoint addresses and the dynamic providers

Yes you can misconfigure with dynamic configuration. Just like any system it can be misconfigured. Always a good idea to test the changes and use source control for your composefile/automation.

Q2.2
This configures the traefik dashboard.
api@internal is the built-in service for the traefik api. It creates and add the basic authentication middleware. This is basically copy paste from Dashboard Dynamic Configuration Examples Operations->Dashboard

Q2.3
It was configured on traefik.localhost change the host rule and it will work on www. If that is what you want.

Q2.4 This is the basic auth middleware credentials. .htpawwsd username:hashedpassword format.

Q3.
The unmatchedwww rule will catch another www.domain.name and it will get a HTTP 418 that is what the noop@internal does. You could replace that with a redirectregex to a landing page for domains without a container instead.

The PathPrefix(`/`) will match any http request as all requests with have a path prefix of /

the priorities are set very low so that they are evaluated last. Priority is otherwise set by the character length of the rule.

1 Like

Thanks for the reply. I tried to make these changes:

traefik.http.routers.dashboard.rule: Host(`www.traefik.localhost`)
traefik.http.routers.dashboard.rule: Host(`www.traefik.localhost`)
traefik.http.routers.dashboard.tls.domains[0].main: traefik.localhost
traefik.http.routers.dashboard.tls.domains[0].sans: www.traefik.localhost

but the dashboard is no longer accessible.

I can't find a solution and don't understand where the error is.
This is my log file:

{"level":"info","msg":"Traefik version 2.8.0 built on 2022-06-29T15:43:58Z","time":"2022-07-31T21:03:04Z"}
{"level":"info","msg":"\nStats collection is disabled.\nHelp us improve Traefik by turning this feature on :)\nMore details on: https://doc.traefik.io/traefik/contributing/data-collection/\n","time":"2022-07-31T21:03:04Z"}
{"level":"warning","msg":"Traefik Pilot is deprecated and will be removed soon. Please check our Blog for migration instructions later this year.","time":"2022-07-31T21:03:04Z"}
{"level":"info","msg":"Starting provider aggregator aggregator.ProviderAggregator","time":"2022-07-31T21:03:04Z"}
{"level":"info","msg":"Starting provider *traefik.Provider","time":"2022-07-31T21:03:04Z"}
{"level":"info","msg":"Starting provider *docker.Provider","time":"2022-07-31T21:03:04Z"}
{"level":"info","msg":"Starting provider *acme.ChallengeTLSALPN","time":"2022-07-31T21:03:04Z"}
{"level":"info","msg":"Starting provider *acme.Provider","time":"2022-07-31T21:03:04Z"}
{"ACME CA":"https://acme-v02.api.letsencrypt.org/directory","level":"info","msg":"Testing certificate renew...","providerName":"leresolver.acme","time":"2022-07-31T21:03:04Z"}
{"entryPointName":"websecure","level":"warning","msg":"No domain found in rule PathPrefix(`/`), the TLS options applied for this router will depend on the SNI of each request","routerName":"websecure-matchlast@docker","time":"2022-07-31T21:03:05Z"}
{"entryPointName":"websecure","level":"warning","msg":"No domain found in rule HostRegexp(`{name:^www\\..*}`), the TLS options applied for this router will depend on the SNI of each request","routerName":"websecure-unmatchedwww@docker","time":"2022-07-31T21:03:05Z"}
{"level":"info","msg":"Register...","providerName":"leresolver.acme","time":"2022-07-31T21:03:06Z"}
{"entryPointName":"websecure","level":"warning","msg":"No domain found in rule PathPrefix(`/`), the TLS options applied for this router will depend on the SNI of each request","routerName":"websecure-matchlast@docker","time":"2022-07-31T21:03:07Z"}
{"entryPointName":"websecure","level":"warning","msg":"No domain found in rule HostRegexp(`{name:^www\\..*}`), the TLS options applied for this router will depend on the SNI of each request","routerName":"websecure-unmatchedwww@docker","time":"2022-07-31T21:03:07Z"}
{"entryPointName":"websecure","level":"warning","msg":"No domain found in rule PathPrefix(`/`), the TLS options applied for this router will depend on the SNI of each request","routerName":"websecure-matchlast@docker","time":"2022-07-31T21:03:13Z"}
{"entryPointName":"websecure","level":"warning","msg":"No domain found in rule HostRegexp(`{name:^www\\..*}`), the TLS options applied for this router will depend on the SNI of each request","routerName":"websecure-unmatchedwww@docker","time":"2022-07-31T21:03:13Z"}
{"ACME CA":"https://acme-v02.api.letsencrypt.org/directory","level":"error","msg":"Unable to obtain ACME certificate for domains \"www.traefik.domain-name.ga\": unable to generate a certificate for the domains [www.traefik.domain-name.ga]: error: one or more domains had a problem:\n[www.traefik.domain-name.ga] acme: error: 400 :: urn:ietf:params:acme:error:dns :: DNS problem: NXDOMAIN looking up A for www.traefik.domain-name.ga - check that a DNS record exists for this domain; DNS problem: NXDOMAIN looking up AAAA for www.traefik.domain-name.ga - check that a DNS record exists for this domain\n","providerName":"leresolver.acme","routerName":"websecure-dashboard@docker","rule":"Host(`www.traefik.domain-name.ga`)","time":"2022-07-31T21:03:13Z"}

I understand what the user and password are for. I don't understand why @cakiwi didn't write it to me.

Today I made other attempts, locally everything works perfectly but on the real server I get the following log. In the browser, the dashboard can be reached without problems and can be reached with a valid https protocol, all redirects work 100%. In the browser, all redirects on the website work but the certificate is not valid.

{"level":"info","msg":"Traefik version 2.8.0 built on 2022-06-29T15:43:58Z","time":"2022-08-02T16:20:42Z"}
{"level":"info","msg":"\nStats collection is disabled.\nHelp us improve Traefik by turning this feature on :)\nMore details on: https://doc.traefik.io/traefik/contributing/data-collection/\n","time":"2022-08-02T16:20:42Z"}
{"level":"warning","msg":"Traefik Pilot is deprecated and will be removed soon. Please check our Blog for migration instructions later this year.","time":"2022-08-02T16:20:42Z"}
{"level":"info","msg":"Starting provider aggregator aggregator.ProviderAggregator","time":"2022-08-02T16:20:42Z"}
{"level":"info","msg":"Starting provider *traefik.Provider","time":"2022-08-02T16:20:42Z"}
{"level":"info","msg":"Starting provider *docker.Provider","time":"2022-08-02T16:20:42Z"}
{"level":"info","msg":"Starting provider *acme.ChallengeTLSALPN","time":"2022-08-02T16:20:43Z"}
{"level":"info","msg":"Starting provider *acme.Provider","time":"2022-08-02T16:20:43Z"}
{"ACME CA":"https://acme-v02.api.letsencrypt.org/directory","level":"info","msg":"Testing certificate renew...","providerName":"leresolver.acme","time":"2022-08-02T16:20:43Z"}
{"entryPointName":"websecure","level":"warning","msg":"No domain found in rule HostRegexp(`{name:^www\\..*}`), the TLS options applied for this router will depend on the SNI of each request","routerName":"websecure-unmatchedwww@docker","time":"2022-08-02T16:20:43Z"}
{"entryPointName":"websecure","level":"warning","msg":"No domain found in rule PathPrefix(`/`), the TLS options applied for this router will depend on the SNI of each request","routerName":"websecure-matchlast@docker","time":"2022-08-02T16:20:43Z"}
{"level":"info","msg":"Register...","providerName":"leresolver.acme","time":"2022-08-02T16:20:45Z"}
{"entryPointName":"websecure","level":"warning","msg":"No domain found in rule PathPrefix(`/`), the TLS options applied for this router will depend on the SNI of each request","routerName":"websecure-matchlast@docker","time":"2022-08-02T16:20:46Z"}
{"entryPointName":"websecure","level":"warning","msg":"No domain found in rule HostRegexp(`{name:^www\\..*}`), the TLS options applied for this router will depend on the SNI of each request","routerName":"websecure-unmatchedwww@docker","time":"2022-08-02T16:20:46Z"}
{"entryPointName":"websecure","level":"warning","msg":"No domain found in rule HostRegexp(`{name:^www\\..*}`), the TLS options applied for this router will depend on the SNI of each request","routerName":"websecure-unmatchedwww@docker","time":"2022-08-02T16:21:00Z"}
{"entryPointName":"websecure","level":"warning","msg":"No domain found in rule PathPrefix(`/`), the TLS options applied for this router will depend on the SNI of each request","routerName":"websecure-matchlast@docker","time":"2022-08-02T16:21:00Z"}
{"entryPointName":"websecure","level":"warning","msg":"No domain found in rule HostRegexp(`{name:^www\\..*}`), the TLS options applied for this router will depend on the SNI of each request","routerName":"websecure-unmatchedwww@docker","time":"2022-08-02T16:21:03Z"}
{"entryPointName":"websecure","level":"warning","msg":"No domain found in rule PathPrefix(`/`), the TLS options applied for this router will depend on the SNI of each request","routerName":"websecure-matchlast@docker","time":"2022-08-02T16:21:03Z"}

On freenom I have this configuration:

Name - Type - TTL - Target
'' - 'A' - '3600' - '123.123.123.123'
'WWW' - 'A' - '3600' - '123.123.123.123'
'TRAEFIK' - 'A' - '3600' - '123.123.123.123'
'WWW.TRAEFIK' - 'A' - '3600' - '123.123.123.123'

There is something wrong?

Hey Milano2022,

It is really hard to follow your questions here. Would you mind creating a new topic for different questions?

You might also find the Community Library helpful, it has many examples of setting up Traefik in Docker.

1 Like

The configuration locally works without problems. When I'm on the line all services work but I don't get valid certificates. I opened this new thread: