Setting up Traefik v2 http-to-https and www-prefix Redirects

This is a guest post by Markus Opolka (martialblog on GitHub).


Traefik is a great tool and its documentation is excellent, however, not all cases can be covered. While upgrading from Traefik v1.7 to v2.1, I found myself digging through various forums, blogs, message boards and whatnot.

This is a summary of all that work. It will show you to do the following:

At the end, our example infrastructure will look like this:

  • Several Docker containers with our example applications
  • A central domain with various Paths for these applications (e.g. example.localhost/my-app and example.localhost/other-app)
  • A central Traefik instance as Reverse Proxy (obviously)
  • A static TLS configuration using openSSL

#  TLS and Docker Config for Testing

In this example we will use example.localhost as Domain, you can simply add this line to /etc/hosts for testing:

1
127.0.0.1 example.localhost

Our Traefik configuration will be in a central directory that will be mounted into the Container:

1
2
3
# We will use this directory for the Traefik configuration
$ mkdir traefik
$ cd traefik

This simple TLS setup will be used in the example:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
# Generate an example Root CA:
$ openssl genrsa -aes256 -out ca.key 2048
$ openssl req -new -x509 -days 7 -key ca.key -sha256 -extensions v3_ca -out ca.crt
Common Name (e.g. server FQDN or YOUR name) []:RootCA

# Generate the domain key:
$ openssl genrsa -out example.localhost.key 2048

# Generate the certificate signing request
$ openssl req -sha256 -new -key example.localhost.key -out example.localhost.csr
Common Name (e.g. server FQDN or YOUR name) []:example.localhost

# Sign the request and generate a certificate
$ openssl x509 -sha256 -req -in example.localhost.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out example.localhost.crt -days 7

# Verify the certificate
$ openssl verify -CAfile ca.crt example.localhost.crt
example.localhost.crt: OK

You can use this docker-compose.yml file to recreate the configuration:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
version: "3.3"
services:
  traefik:
    # Adjust to your Traefik version
    image: "traefik:v2.1"
    ports:
      - "80:80"
      - "8080:8080"
      - "443:443"
    volumes:
      - "/var/run/docker.sock:/var/run/docker.sock:ro"
      # Adjust to your working directory:
      - "traefik:/etc/traefik:ro"
  my-app:
    image: containous/whoami:v1.5.0
    labels:
      - "traefik.http.routers.my-app.rule=Host(`example.localhost`) && PathPrefix(`/my-app`)"
  other-app:
    image: containous/whoami:v1.5.0
    labels:
      - "traefik.http.routers.other-app.rule=Host(`example.localhost`) && PathPrefix(`/other-app`)"
      # Just an example on how to add Middlewares
      - "traefik.http.middlewares.strip-other-app.stripprefix.prefixes=/other-app"
      - "traefik.http.routers.other-app.middlewares=strip-other-app@docker"

#  How to configure a global http-to-https redirect

#  Traefik v2.1

Coming from Traefik v1.7, there were a lot of changes that had to be done. All migration details can be found here. In Traefik v1 we could simply add a redirect in the entrypoint via [entryPoints.http.redirect], this was not an option in Traefik v2.1. (Spoiler: since Traefik v2.2 this is back, see below).

For all following examples we will use the previously described docker-compose.yml file with Traefik v2.1.

First we create our main configuration file for Traefik:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# traefik.toml
[providers.docker]
  endpoint = "unix:///var/run/docker.sock"

[log]
  level = "DEBUG"

[entryPoints]
  [entryPoints.http]
    address = ":80"
  [entryPoints.https]
    address = ":443"

[providers.file]
  directory = "/etc/traefik/dynamic/"

In the traefik.toml we define some defaults:

  • The path to the Docker socket
  • The log level will be at Debug

Then we configure two entrypoints, one for http and one for https. As well as a file provider for some global configuration.

Now let’s create the dynamic directory for the global redirect configuration:

1
2
3
4
5
$ pwd
~/home/traefik

$ mkdir dynamic
$ touch dynamic/redirects.toml

Within the redirects.toml we first define our global TLS config. Meaning all sites will simply us the same TLS certificate.

Afterwards, we add the configuration to redirect all http requests to https with:

  • A Middleware that will redirect the Scheme to https
  • A dummy Service that is just there because every Router needs a Service
  • A catch-all Router with the dummy Service and the redirect Middleware attached to it

The redirects.toml should look like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# redirects.toml
[[tls.certificates]]
  certFile = "/etc/traefik/example.localhost.crt"
  keyFile = "/etc/traefik/example.localhost.key"
[tls.options]
  [tls.options.default]
    minVersion = "VersionTLS12"

[http.routers]
  [http.routers.https-redirect]
    rule = "HostRegexp(`{any:.*}`)"
    middlewares = ["https-redirect"]
    service = "noop"
    entryPoints = ["http"]

[http.middlewares]
  [http.middlewares.https-redirect.redirectscheme]
    scheme = "https"

[http.services]
  [http.services.noop.LoadBalancer]
     [[http.services.noop.LoadBalancer.servers]]
        url = ""

With the main and redirect configuration in place, we can add labels to our Docker containers. The new Docker labels should look like this:

1
2
3
4
5
6
7
8
9
labels:
  - "traefik.http.routers.my-app.rule=Host(`example.localhost`) && PathPrefix(`/my-app`)"
  - "traefik.http.routers.my-app.entrypoints=https"
  - "traefik.http.routers.my-app.tls=true"

labels:
  - "traefik.http.routers.other-app.rule=Host(`example.localhost`) && PathPrefix(`/other-app`)"
  - "traefik.http.routers.other-app.entrypoints=https"
  - "traefik.http.routers.other-app.tls=true"

After starting the containers with this configuration (docker-compose up), our applications are now available and all http requests are redirected to https:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
$ curl -Ik http://example.localhost/my-app
HTTP/1.1 308 Permanent Redirect
Location: https://example.localhost/my-app

$ curl -Ik https://example.localhost/my-app
HTTP/2 200

$ curl -Ik http://example.localhost/other-app
HTTP/1.1 308 Permanent Redirect
Location: https://example.localhost/other-app

#  Traefik v2.2

Good news, you can use the previous configuration for Traefik v2.2. Even better news, Traefik v2.2 added some changes to make http-to-https redirects way simpler, see this Pull Request.

Now we can simply define redirects in the traefik.toml EntryPoint configuration:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
[entryPoints]
  [entryPoints.http]
    address = ":80"
    [entryPoints.http.http]
      [entryPoints.http.http.redirections]
        [entryPoints.http.http.redirections.entrypoint]
          to = "https"
          scheme = "https"
  [entryPoints.https]
    address = ":443"

With this, we can remove almost everything from the redirects.toml and leave just the central TLS configuration.

The redirects.toml should now look like this:

1
2
3
4
5
6
[[tls.certificates]]
  certFile = "/etc/traefik/example.localhost.crt"
  keyFile = "/etc/traefik/example.localhost.key"
[tls.options]
  [tls.options.default]
    minVersion = "VersionTLS12"

Everything else remains the same and http requests are redirected to https:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
$ curl -Ik http://example.localhost/my-app
HTTP/1.1 308 Permanent Redirect
Location: https://example.localhost/my-app

$ curl -Ik https://example.localhost/my-app
HTTP/2 200

$ curl -Ik http://example.localhost/other-app
HTTP/1.1 308 Permanent Redirect
Location: https://example.localhost/other-app

#  How to configure a redirect from and to a www-prefix

Let’s assume, in addition to our applications, we have a central landing page on an external system and Traefik will redirect to it. This page should be available with and without the www-prefix. So the infrastructure looks like this:

  • example.localhost/my-app via Docker
  • example.localhost/other-app via Docker
  • example.localhost and www.example.localhost via an external webserver

#  Traefik v2.*

To configure this a redirect to a www-prefix, we adjust the dynamic/redirects.toml file and add:

  • A Middleware that will redirect via a Regex
  • A Router with the new Middleware attached to it and the same dummy Service as before

The dynamic/redirects.toml should now look like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
[[tls.certificates]]
  certFile = "/etc/traefik/example.localhost.crt"
  keyFile = "/etc/traefik/example.localhost.key"
[tls.options]
  [tls.options.default]
    minVersion = "VersionTLS12"

[http.routers]
  [http.routers.default-redirects]
    rule = "Host(`example.localhost`) && Path(`/`)"
    middlewares = ["www-redirect"]
    service = "noop"
    entryPoints = ["https", "http"]
    [http.routers.default-redirects.tls]

[http.middlewares]
  [http.middlewares.www-redirect.redirectregex]
    regex = "^https?://example.localhost"
    replacement = "https://www.example.localhost"
    permanent = true

[http.services]
  [http.services.noop.LoadBalancer]
     [[http.services.noop.LoadBalancer.servers]]
        url = ""

Since we have nothing listening on https://www.example.localhost the request fails, but the redirect is still in place:

1
2
3
curl -Ik https://example.localhost
HTTP/2 308
location: https://www.example.localhost/

To remove a www-prefix we can simply swap the RedirectRegex.

1
2
regex = "^https?://www.example.localhost"
replacement = "https://example.localhost"

#  References