Http to https redirection difference in using CLI vs middelwares

Hello there. AFAIU, you can redirect http to https in (at least) 2 ways.

Using CLI (ie. statically):

  - --entrypoints.webinsecure.address=:80
  # redirection to https
  - --entrypoints.webinsecure.http.redirections.entrypoint.to=websecure
  - --entrypoints.webinsecure.http.redirections.entrypoint.scheme=https
  - --entrypoints.websecure.address=:443

Using middlewares (ie. dynamically)

  # Global redirection: http to https
  traefik.http.routers.http_catchall.rule: HostRegexp(`{host:.+}`)
  traefik.http.routers.http_catchall.entrypoints: web
  traefik.http.routers.http_catchall.middlewares: tohttps

I'm just wondering if there is any difference in such approaches or if they are equivalent from the functional point of view.

Side question: with the CLI option, is there a way to add also a rule for redirecting URLs like http(s)://www.foo.com to https://foo.com

The functional difference would be that with the middleware approach another http router can still bind a route to the entryPoint.

No. I set that up as another rule/middleware that is handling foo.com

Hi @cakiwi, thanks for your hints. Regarding the second point, I'm trying to mix up

  • http to https redirect at the CLI level
    with
  • www to non-www redirect at the middleware level

This is the CLI section of the Traefik container:

  - --entrypoints.webinsecure.address=:80
  # redirection to https
  - --entrypoints.webinsecure.http.redirections.entrypoint.to=websecure
  - --entrypoints.webinsecure.http.redirections.entrypoint.scheme=https
  - --entrypoints.websecure.address=:443

and this is the middleware section of the Traefik container (labels):

# just for https, since Traefik redirects all http connections to https at the entrypoint level
traefik.http.middlewares.trim_www.redirectregex.regex: ^https://www\.(.+)
traefik.http.middlewares.trim_www.redirectregex.replacement: https://$${1}
traefik.http.middlewares.trim_www.redirectregex.permanent: true

But this configuration does not work. Where am I wrong?

Try:

traefik.http.middlewares.trim_www.redirectregex.regex: ^https://www\\.(.+)

Hey @cakiwi, thanks but unfortunately that did not work (I still get a 404 with Traefik v2.2.1). This is the outcome:

# entrypoint redirection (it works!)
$ curl -I www.foo.com
HTTP/1.1 308 Permanent Redirect
Location: https://www.foo.com/
Date: Mon, 13 Jul 2020 07:33:57 GMT
Content-Length: 18
Content-Type: text/plain; charset=utf-8

# trim www (it does not work!)
$ curl -I https://www.foo.com
HTTP/2 404
content-type: text/plain; charset=utf-8
x-content-type-options: nosniff
content-length: 19
date: Mon, 13 Jul 2020 07:34:06 GMT

And this is my configuration (excerpt):

command:
       - --entrypoints.webinsecure.address=:80
      # redirection to https
      - --entrypoints.webinsecure.http.redirections.entrypoint.to=websecure
      - --entrypoints.webinsecure.http.redirections.entrypoint.scheme=https
      - --entrypoints.websecure.address=:443
labels:
      # just for https, since Traefik redirects all http connections to https at the entrypoint level
      traefik.http.middlewares.trim_www.redirectregex.regex: ^https://www\\.(.+)
      traefik.http.middlewares.trim_www.redirectregex.replacement: https://$${1}
      traefik.http.middlewares.trim_www.redirectregex.permanent: true

The middleware looks good. Hard to say what is happening without the full config.

Here is a simple example:

WWW redirect
  whoami:
    image: containous/whoami
    labels:
      traefik.enable: "true"
      traefik.http.routers.w.rule: Host(`myservice.localhost`,`www.myservice.localhost`)
      traefik.http.routers.w.entrypoints: websecure
      traefik.http.routers.w.middlewares: wwwredir
      traefik.http.middlewares.wwwredir.redirectregex.regex: ^https://www\.(.+)
      traefik.http.middlewares.wwwredir.redirectregex.replacement: https://$${1}
curl output
curl --insecure --include --location www.myservice.localhost 
HTTP/1.1 301 Moved Permanently
Location: https://www.myservice.localhost/
Date: Mon, 13 Jul 2020 11:59:45 GMT
Content-Length: 17
Content-Type: text/plain; charset=utf-8

HTTP/2 302 
location: https://myservice.localhost/
content-type: text/plain; charset=utf-8
content-length: 5
date: Mon, 13 Jul 2020 11:59:45 GMT

HTTP/2 200 
content-type: text/plain; charset=utf-8
date: Mon, 13 Jul 2020 11:59:45 GMT
content-length: 361

Hostname: c2bdab87fd85
IP: 127.0.0.1
IP: 172.25.0.3
RemoteAddr: 172.25.0.2:34484
GET / HTTP/1.1
Host: myservice.localhost
User-Agent: curl/7.58.0
Accept: */*
Accept-Encoding: gzip
X-Forwarded-For: 172.25.0.1
X-Forwarded-Host: myservice.localhost
X-Forwarded-Port: 443
X-Forwarded-Proto: https
X-Forwarded-Server: 02480333cddf
X-Real-Ip: 172.25.0.1

'Traefik accesslog`
traefik_1  | 172.25.0.1 - - [13/Jul/2020:11:59:45 +0000] "GET / HTTP/1.1" 301 17 "-" "-" 1 "web-to-websecure@internal" "-" 4ms
traefik_1  | 172.25.0.1 - - [13/Jul/2020:11:59:45 +0000] "GET / HTTP/2.0" 302 5 "-" "-" 2 "w@docker" "-" 0ms
traefik_1  | 172.25.0.1 - - [13/Jul/2020:11:59:45 +0000] "GET / HTTP/2.0" 200 361 "-" "-" 3 "w@docker" "http://172.25.0.3:80" 9ms

Ok, looks good thanks.

However I'm trying to use this strategy globally. I have one central Traefik container with this configuration:

docker-compose.yml (Traefik)
version: '3.7'

services:
  # a cool reverse-proxy / load balancer
  traefik:
    # The official v2 Traefik docker image
    image: traefik:v2.2.1
    container_name: traefik
    security_opt:
      - no-new-privileges:true
    restart: always
    env_file:
      # variables that will be passed to the container
      - .provider.env
    volumes:
      # So that Traefik can listen to the Docker events
      - /var/run/docker.sock:/var/run/docker.sock:ro
      # tls-certificates path (TLS)
      - ./letsencrypt:/letsencrypt
    ports:
      # http
      - ${HTTP_PORT}:80
      # https
      - ${HTTPS_PORT}:443
    command:
      ###########################################
      #   Static Configuration harnessing CLI   #
      ###########################################
      # Activate dashboard.
      - --api.dashboard=true

      # Log level set to traefik logs. (Default: ERROR)
      - --log.level=${DEBUG_LEVEL}

      # Enable Docker backend with default settings.
      - --providers.docker=true
      # Do not expose containers by default.
      - --providers.docker.exposedbydefault=false
      # Default Docker network used.
      - --providers.docker.network=proxy

      # --entrypoints.<name>.address for ports
      # 80 (i.e., name = webinsercure)
      - --entrypoints.webinsecure.address=:80
      # redirection to https
      - --entrypoints.webinsecure.http.redirections.entrypoint.to=websecure
      - --entrypoints.webinsecure.http.redirections.entrypoint.scheme=https
      # 443 (i.e., name = websecure)
      - --entrypoints.websecure.address=:443

      # DNS certificatesresolvers w/ Let's encrypt 
      # ACME V2 supports wildcard certificates.
      # Wildcard certificates can only be generated through a DNS-01 challenge.
      - --certificatesresolvers.leresolver.acme.dnschallenge=true
      - --certificatesresolvers.leresolver.acme.dnsChallenge.provider=godaddy
      - --certificatesresolvers.leresolver.acme.dnsChallenge.delayBeforeCheck=0
      # Email address used for registration.
      - --certificatesresolvers.leresolver.acme.email=${CERTIFICATES_EMAIL}
      # Certificates storage
      - --certificatesresolvers.leresolver.acme.storage=/letsencrypt/acme.json


      # TLS certificatesresolvers w/ Let's encrypt (for other domains such as science4fun.org)
      # See here: https://github.com/containous/traefik/issues/5632
      # Use TLS challenge for `tlsleresolver`
      - --certificatesresolvers.tlsleresolver.acme.tlschallenge=true
      # Email address used for registration.
      - --certificatesresolvers.tlsleresolver.acme.email=${CERTIFICATES_EMAIL}
      # Certificates storage (not sure if you can store all the certs in the same file)
      # Let's use a new one for the moment
      - --certificatesresolvers.tlsleresolver.acme.storage=/letsencrypt/acme_tls.json

    networks:
      # This is the network over which Traefik communicates with other containers.
      - proxy

    labels:
      ################################################
      #   Dynamic configuration with Docker Labels   #
      ################################################
      # You can tell Traefik to consider (or not) this container by setting traefik.enable to true or false.
      # We need it for the dashboard
      traefik.enable: true

      # Dashboard
      traefik.http.routers.traefik.rule: Host(`traefik.foo.com`)
      traefik.http.routers.traefik.service: api@internal
      traefik.http.routers.traefik.tls.certresolver: leresolver
      traefik.http.routers.traefik.tls.domains[0].main: foo.com
      traefik.http.routers.traefik.tls.domains[0].sans: '*.foo.com'
      traefik.http.routers.traefik.entrypoints: websecure
      # Declaring the user list
      #
      # Note=all dollar signs in the hash need to be doubled for escaping.
      # To create user:password pair, it's possible to use this command:
      # echo $(htpasswd -nb user password) and then save user and password in
      # the .env file
      traefik.http.routers.traefik.middlewares: traefik-auth
      traefik.http.middlewares.traefik-auth.basicauth.users: ${DASHBOARD_USERNAME}:${DASHBOARD_PASSWORD}

networks:
  proxy:
    external: true

... and I was trying to put there the logic for all non-www redirections, with a global fashion, without repeating it in all other services defined in other compose files (like you did with whoami in your example). Not sure if this is possible since we are working at the dynamic level.

Haven't looked at the compose, just addressing the comment:

You can add default middlewares to an entrypoint

If you are using a Host rule you would still have to match www.service.domain.com as well as service.domain.com.

1 Like

Thanks for your suggestion but I'm not there, yet.

My rationale:

  • Redirect http (webinsecure) to https (websecure) playing with entrypoints
  • Add a default middleware, as suggested, to the websecure entrypoint
  • Use such a middleware to strip www from requests
  • Forward just non-www requests

In order to do that, I borrowed the global middleware described in this answer by @ldez (Global redirect www to non-www with HTTPS redirection). This is the main Traefik container now:

command:
      # --entrypoints.<name>.address for ports
      # 80 (i.e., name = webinsercure)
      - --entrypoints.webinsecure.address=:80
      # redirection to https
      - --entrypoints.webinsecure.http.redirections.entrypoint.to=websecure
      - --entrypoints.webinsecure.http.redirections.entrypoint.scheme=https
      # 443 (i.e., name = websecure)
      - --entrypoints.websecure.address=:443
      # default middleware
      - --entrypoints.websecure.http.middlewares=wwwtohttps@docker
labels:
      # middleware: http(s)://(www.) to  https://
      traefik.http.middlewares.wwwtohttps.redirectregex.regex: ^https?://(?:www\.)?(.+)
      traefik.http.middlewares.wwwtohttps.redirectregex.replacement: https://$${1}
      traefik.http.middlewares.wwwtohttps.redirectregex.permanent: true

Everything looks ok:

Then I have services like this one

  my-test-app:
    image: containous/whoami
    networks:
      # This is the network over which Traefik communicates with other containers.
      - proxy
    labels:
      traefik.enable: true
      traefik.http.routers.my-test-app.rule: Host(`foo.com`)
      traefik.http.routers.my-test-app.entrypoints: websecure
      traefik.http.routers.my-test-app.tls.certresolver: leresolver
      traefik.http.routers.my-test-app.tls.domains[0].main: foo.com
      traefik.http.routers.my-test-app.tls.domains[0].sans: '*.foo.com'

But I still get 404 for www.foo.com.

Please, note that, from the theoretical perspective :smiley: , I shouldn't set explicitly "www.foo.com"-like rules in each router rule, since the global middleware (wwwtohttps) should strip www from requests. (Also in this answer, they do the same)

How can I make this scenario work fine?

It appears to me that you are getting 404 on www.foo.com because there is no router rule that matches requests with this address (or you are not showing it in your post above). In order for router middleware to kick in, the request has to arrive to the router first, and yours does not.

2 Likes

Aha moment :tada:

the request has to arrive to the router first, and yours does not.

I was wrongly trying to borrow the following working example with redirection at the entrypoint level.

working docker-compose.yml
version: '3.7'

services:
  traefik:
    image: traefik:v2.1.3
    command:
      - --entrypoints.web.address=:80
      - --entrypoints.websecure.address=:443
      - --providers.docker=true
      - --providers.docker.exposedbydefault=false
      - --log.level=INFO
    ports:
      - 80:80
      - 443:443
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
    labels:
      traefik.enable: true

      # Global redirection: http to https
      traefik.http.routers.http-catchall.rule: HostRegexp(`{host:(www\.)?.+}`)
      traefik.http.routers.http-catchall.entrypoints: web
      traefik.http.routers.http-catchall.middlewares: wwwtohttps
      
      # Global redirection: https (www.) to https
      traefik.http.routers.wwwsecure-catchall.rule: HostRegexp(`{host:(www\.).+}`)
      traefik.http.routers.wwwsecure-catchall.entrypoints: websecure
      traefik.http.routers.wwwsecure-catchall.tls: true
      traefik.http.routers.wwwsecure-catchall.middlewares: wwwtohttps

      # middleware: http(s)://(www.) to  https://
      traefik.http.middlewares.wwwtohttps.redirectregex.regex: ^https?://(?:www\.)?(.+)
      traefik.http.middlewares.wwwtohttps.redirectregex.replacement: https://$${1}
      traefik.http.middlewares.wwwtohttps.redirectregex.permanent: true

  whoami:
    image: containous/whoami
    labels:
      traefik.enable: true
      traefik.http.routers.whoami.rule: Host(`whoami.localhost`)
      traefik.http.routers.whoami.entrypoints: websecure
      traefik.http.routers.whoami.tls: true

There, they use 2 main routers to catch and redirect http:// and https://www requests, and a middleware to remove the www. In my example, as you pointed out, no router can handle requests with the www.

Since I want to remove www globally, I believe I'll replace entrypoint redirections with the 2 global routers + the middleware of the example. This avoids the need of specifying rules for both www and non-www per service router (e.g., whoami).

@zespri and @cakiwi, many thanks for your great help!

1 Like