Skip to content

Instantly share code, notes, and snippets.

@kekru
Last active March 12, 2024 15:15
Show Gist options
  • Star 35 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save kekru/d088be6a3fa844089ae62d80c077bb38 to your computer and use it in GitHub Desktop.
Save kekru/d088be6a3fa844089ae62d80c077bb38 to your computer and use it in GitHub Desktop.
Traefik redirect / (root) to sub path with Docker labels

Traefik: redirect base or root path to a subpath

This is tested with Traefik 1.7

This is how to redirect the root or base path to a sub path in Traefik using Docker labels:
Goals

  • https://example.com -> https://example.com/abc/xyz/
  • https://example.com/ -> https://example.com/abc/xyz/
  • https://example.com/something -> no redirect

We will match <begin of line>https://<any chars but not slash AS group1><slash or nothing><end of line>
and replace it with https://<group1>/abc/xyz/.
In regex we have to escape a / character by \/. In docker-compose labels we need to escape again, so that it becomes \\\\/.
We also need to escape $ to $$ because of docker-compose.

labels:
  - "traefik.frontend.rule=Host:example.com"
  - "traefik.frontend.redirect.regex=^https:\\\\/\\\\/([^\\\\/]+)\\\\/?$$"
  - "traefik.frontend.redirect.replacement=https://$$1/abc/xyz/"
  - "traefik.port=80"
  - "traefik.enable=true"
@chuckmckinnon
Copy link

Thank you, thank you, thank you!!

@aaronchn
Copy link

Following up: Thank you, thank you, thank you!! :)

@gustavomtborges
Copy link

On Traefik v2.x I had to escape only once, like this

        - "traefik.http.middlewares.add-context.redirectregex.regex=^https:\\/\\/([^\\/]+)\\/?$$"
        - "traefik.http.middlewares.add-context.redirectregex.replacement=https://$$1/xyz"

@SteffRhes
Copy link

Thank you for this gist. I want to extend it a bit, since I'm a beginner with traeffik and was struggling until I figured out that the respective middleware must be defined too.

So I want to add my full docker-compose.yml as example for other beginners to provide context to your regex logic defined in redirectregex.regex and redirectregex.replacement (the relevant parts to this gist are the last few lines with comments):

version: "3.3"

services:

  traefik:
    image: "traefik:v2.2"
    container_name: "traefik"
    command:
      - "--log.level=DEBUG"
      - "--api.insecure=true"
      - "--providers.docker=true"
      - "--providers.docker.exposedbydefault=false"
      - "--entrypoints.websecure.address=:443"
      - "--certificatesresolvers.myresolver.acme.tlschallenge=true"
      - "--certificatesresolvers.myresolver.acme.email=whatever@example.com"
      - "--certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json"
    ports:
      - "443:443"
      - "8080:8080"
    volumes:
      - "./letsencrypt:/letsencrypt"
      - "/var/run/docker.sock:/var/run/docker.sock:ro"

  service_nextcloud:
    image: nextcloud:latest
    container_name: container_nextcloud
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.service_nextcloud.rule=Host(`example.com`)"
      - "traefik.http.routers.service_nextcloud.entrypoints=websecure"
      - "traefik.http.routers.service_nextcloud.tls.certresolver=myresolver"

      # the middleware 'add-context' must be defined so that the regex rules can be attached to it
      - "traefik.http.routers.service_nextcloud.middlewares=add-context"

      # here is the logic provided by gustavomtborges of this gist:
      - "traefik.http.middlewares.add-context.redirectregex.regex=^https:\\/\\/([^\\/]+)\\/?$$"
      - "traefik.http.middlewares.add-context.redirectregex.replacement=https://$$1/xyz"

@kekru
Copy link
Author

kekru commented Aug 17, 2020

@steffres thanks for your config example.
For better understanding: My config is for Traefik 1.x and your config is for Traefik 2.x

The config changed a lot between the major versions 1 and 2

@kopax
Copy link

kopax commented Sep 23, 2020

How to do with v2? I tested every snippet here nothing worked.

@glaudiston
Copy link

Traefik changed a lot in 2.x

Now you need to have:

  • A entrypoint that expose ports to external network.
  • A router to define route filters and link the entrypoint and the middlewares for a service
  • A middleware where you can process the request before the service receive the request
  • A service where you set the loadbalancer and endpoints for your service backend

For me was not a easy task to make the migration from 1.5 to 2.x, but once you figure out the new arch, It's not so hard. You just need to get what they are saying in the manual.

I did almost all the things i needed, but until now I was unable to set the router to the root path (/) without breaking all.

If I set the router rule to Path("/") traefik stop working.

@kekru
Copy link
Author

kekru commented Sep 24, 2020

I can't really help for v2. I did not have time to migrate my setups to v2 yet

@SteffRhes
Copy link

SteffRhes commented Sep 25, 2020

How to do with v2? I tested every snippet here nothing worked.

In my case I needed to have the root domain point to a sub url which is a public folder of my nextcloud instance (without this redirect being shown via the url to the visitor, so this can be a bit of a different use-case than this original gist intents to do). My full docker-compose.yml which is working for this can be found here: https://help.nextcloud.com/t/move-nextcloud-to-sub-url-but-put-a-public-folder-at-root-with-nextcloud-nginx-nginx-docker-gen-all-in-docker/89292/15?u=steffres

The most interesting parts of this compose should be (which of course would still need the context of the rest):

      - "traefik.http.routers.service_nextcloud.middlewares=custom_repath"
      - "traefik.http.middlewares.custom_repath.replacepathregex.regex=^/$$"
      - "traefik.http.middlewares.custom_repath.replacepathregex.replacement=/intern/s/63KFWGkziffJR8j"

Note that therein, in the first line here a middleware must be defined and named, in my case custom_repath which then later is refered to by e.g. traefik.http.middlewares.custom_repath.replacepathregex.regex=... (I found this a bit tricky that defining your own named thing and referencing this thing is not fully clear from the code if you are referencing a custom defined thing or something pre-defined because it's squeezed into the whole line)

And then in the second line, the pattern logic is defined and in the third the replacement. Also note that $$ is a double escaping $ because it's also a special character in docker-compose.

So in my case this logic replaces cloud.museumsstrasse.at/ with cloud.museumsstrasse.at/intern/s/63KFWGkziffJR8j (while this not being shown to the visitor, I don't know if that is what you want ... ) but nothing else, e.g. cloud.museumsstrasse.at/intern is not replaced.

If you want to keep the redirected url shown to the visitor, than a slight change must be made, but I don't remember what anymore.

@glaudiston
Copy link

steffres, first all, thank you for you help.

If I set the router rule to Path("/") traefik stop working.

  • Healthcheck was killling the container, 🤦 ... after fix it, traefik became stable.

In the end I made it work with:

sudo docker service update foundation-proxy --force --replicas=1 \
    --label-add 'traefik.http.routers.foundation-proxy-root.rule=Path(`/`)' \
    --label-add 'traefik.http.middlewares.foundation-proxy-root.redirectregex.regex=.*' \
    --label-add 'traefik.http.middlewares.foundation-proxy-root.redirectregex.replacement=/foundation' \
    --label-add 'traefik.http.routers.foundation-proxy-root.middlewares=foundation-proxy-root@docker'

The addprefix middleware could be a alternative but:

  • "addprefix cannot be a standalone element" because I miss the .prefix= part on label;
  • after fix, the label, it started causing HTTP 504 again... then after carefully look the log, I figured out that docker swarm keeps some dirt config for traefik and traefik had the addprefix and replacepathregex, then after a docker service update --force It worked for some time but traefik stopped after some seconds loggin 499 Client Closed Request' caused by: context canceled.

@axeII
Copy link

axeII commented Mar 14, 2021

@gustavomtborges thanks a lot!

@flippy1345
Copy link

I leave this her just in case someone like me is looking for a way to have this in v2 and Kubernetes

apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
  name: traefik-dashboard-redirect
  namespace: default
spec:
  redirectRegex:
    regex: ^https:\/\/([^\/]+)\/?$
    replacement: https://${1}/dashboard/

This would allow access to the traefik dashboard via "https://traefik.foo.bar"
The URL would change like this: "https://traefik.foo.bar" -> "https//traefik.foo.bar/dashboard/"

@airtonix
Copy link

airtonix commented Oct 3, 2022

I leave this her just in case someone like me is looking for a way to have this in v2 and Kubernetes

apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
  name: traefik-dashboard-redirect
  namespace: default
spec:
  redirectRegex:
    regex: ^https:\/\/([^\/]+)\/?$
    replacement: https://${1}/dashboard/

This would allow access to the traefik dashboard via "https://traefik.foo.bar" The URL would change like this: "https://traefik.foo.bar" -> "https//traefik.foo.bar/dashboard/"

Thanks for the effort, but this just ends up with an infinite loop.

@flippy1345
Copy link

I leave this her just in case someone like me is looking for a way to have this in v2 and Kubernetes

apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
  name: traefik-dashboard-redirect
  namespace: default
spec:
  redirectRegex:
    regex: ^https:\/\/([^\/]+)\/?$
    replacement: https://${1}/dashboard/

This would allow access to the traefik dashboard via "https://traefik.foo.bar" The URL would change like this: "https://traefik.foo.bar" -> "https//traefik.foo.bar/dashboard/"

Thanks for the effort, but this just ends up with an infinite loop.

It can be because I redirect hard to https.
This is my complete configuration with https redirect, regex path redirect and ip whitelist:

-- Ingress --
traefik-dashboard

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
    traefik.ingress.kubernetes.io/router.middlewares: default-traefik-dashboard-chain@kubernetescrd
  name: traefik-dashboard
  namespace: kube-system
spec:
  rules:
  - host: traefik.DOMAIN.de
    http:
      paths:
      - backend:
          service:
            name: traefik-dashboard
            port:
              number: 9000
        pathType: ImplementationSpecific
  tls:
  - hosts:
    - traefik.DOMAIN.de
    secretName: traefik-DOMAIN-de-tls

-- Traefik Middleware --
traefik-dashboard-chain

apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
  name: traefik-dashboard-chain
  namespace: default
spec:
  chain:
    middlewares:
    - name: internal-dashboard-chain
    - name: traefik-dashboard-redirect

internal-dashboard-chain

apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
  name: internal-dashboard-chain
  namespace: default
spec:
  chain:
    middlewares:
    - name: redirect-https
    - name: ip-whitelist

redirect-https

apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
  name: redirect-https
  namespace: default
spec:
  redirectScheme:
    permanent: true
    scheme: https

ip-whitelist

apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
  name: ip-whitelist
  namespace: default
spec:
  ipWhiteList:
    sourceRange:
    - 49.XXX.XXX.XXX #My Home IP

traefik-dashboard-redirect

apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
  name: traefik-dashboard-redirect
  namespace: default
spec:
  redirectRegex:
    regex: ^https:\/\/([^\/]+)\/?$
    replacement: https://${1}/dashboard/

@idhamari
Copy link

idhamari commented Mar 23, 2023

Thanks, everyone for your contributions. I just started trying Traefik today and unfortunately, the above solutions do not work for me. Here is my setup:

two very simple static webapps, each app running in a separate container

        app1/
                index.html
                subfolder/index.html
        app2/
                index.html
                subfolder/index.html

Here are the contents of the html files:

    <!DOCTYPE html>
    <html>
    <head>
    <title>app1</title>
    </head>
    <body>
    <h1>App1 website home </h1>
    <p>Test reverse proxy !</p>
    <p><a href="subfolder/index.html">App1 subfolder</a></p>
    </body>
    </html>

    <!DOCTYPE html>
    <html>
    <head>
    <title>app1 subfolder</title>
    </head>
    <body>
    <h1>App1 website subfolder </h1>
    <p>Test reverse proxy subfolder webpage!</p>
    <p><a href="../index.html">App1 Home</a></p>
    </body>
    </html>

I am trying to access app1 and app2 via something like

        https://myexample.com/app1
        https://myexample.com/app2

With the setup below, I can access the main page of each app but not the subfolders.

        version: "3.3"

        services:

          traefik:
            image: "traefik:latest"
            container_name: traefik
            command:
              - "--log.level=DEBUG"
              - "--api.insecure=true"
              - "--providers.docker=true"
              - "--providers.docker.exposedbydefault=false"
              - "--entrypoints.web.address=:80"
            ports:
              - "80:80"
              - "8080:8080"
            volumes:
              - "/var/run/docker.sock:/var/run/docker.sock:ro"

          app1:
            image: "nginxdemos/hello"
            container_name: app1
            scale: 1
            volumes:
                - ./vols/app1:/usr/share/nginx/html

            labels:
              - "traefik.enable=true"
              - "traefik.http.routers.app1.entrypoints=web"
              - "traefik.http.routers.app1.rule=Host(`myexample.com`) && Path(`/app1`,`/app1/`)"
              - "traefik.http.routers.app1.middlewares=add-context"
              - "traefik.http.middlewares.add-context.redirectregex.regex=^https:\\/\\/([^\\/]+)\\/?$$"
              - "traefik.http.middlewares.add-context.redirectregex.replacement=https://$$1/app1/"
          app2:
            image: "nginxdemos/hello"
            container_name: app2
            scale: 1
            volumes:
                - ./vols/app2:/usr/share/nginx/html
            labels:
              - "traefik.enable=true"
              - "traefik.http.routers.app2.entrypoints=web"                  
              - "traefik.http.routers.app2.rule=Host(`myexample.com`) && Path(`/app2/`)"
              - "traefik.http.routers.app2.middlewares=add-context"
              - "traefik.http.middlewares.add-context.redirectregex.regex=^https:\\/\\/([^\\/]+)\\/?$$"
              - "traefik.http.middlewares.add-context.redirectregex.replacement=https://$$1/app2/"

The routing seems to work, e.g. I can see in the browser address bar:

          https://myexample.com/app1/subfolder/index.html 

But the page is not displayed (seems the path is not correct) and I get the 404 page not found error.

My network setup as follows:

  • Mainserver connected to a domain myexample.com
  • the Mainserver has nginx that sends incoming requests to the traefik machine 80 port using reverse proxy.

What I am missing?

@flippy1345
Copy link

flippy1345 commented Mar 25, 2023

@idhamari ,
my first guess would be a trailing / to much or to few

Also try to use the following:

  - "traefik.http.middlewares.add-context.redirectregex.regex=^https:\/\/([^\/]+)\/?$"
  - "traefik.http.middlewares.add-context.redirectregex.replacement=https://${1}/app1/"

  - "traefik.http.middlewares.add-context.redirectregex.regex=^https:\/\/([^\/]+)\/?$"
  - "traefik.http.middlewares.add-context.redirectregex.replacement=https://${1}/app2/"

@idhamari
Copy link

@flippy1345 thanks for your reply. Still nothing works.

@yunkuangao
Copy link

Thank you sincerely.

@kekru
Copy link
Author

kekru commented Apr 9, 2023

@idhamari sorry for late answer, but for me this is working:

services:

  traefik:
    image: "traefik:2.9.10"
    container_name: traefik
    command:
      - "--log.level=DEBUG"
      - "--api.insecure=true"
      - "--providers.docker=true"
      - "--providers.docker.exposedbydefault=false"
      - "--entrypoints.web.address=:80"
    ports:
      - "80:80"
      - "8080:8080"
    volumes:
      - "/var/run/docker.sock:/var/run/docker.sock:ro"

  app1:
    image: traefik/whoami:v1.8.7
    container_name: app1
    scale: 1
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.app1.entrypoints=web"
      - "traefik.http.routers.app1.rule=PathPrefix(`/app1`)"
      - "traefik.http.routers.app1.middlewares=add-context1"
      - "traefik.http.middlewares.add-context1.redirectregex.regex=^(.+/app1)$$"
      - "traefik.http.middlewares.add-context1.redirectregex.replacement=$$1/"
  app2:
    image: traefik/whoami:v1.8.7
    container_name: app2
    scale: 1
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.app2.entrypoints=web"                  
      - "traefik.http.routers.app2.rule=PathPrefix(`/app2`)"
      - "traefik.http.routers.app2.middlewares=add-context2"
      - "traefik.http.middlewares.add-context2.redirectregex.regex=^(.+/app2)$$"
      - "traefik.http.middlewares.add-context2.redirectregex.replacement=$$1/"

Changes that I made:

  • removed Host(myexample.com), because you only have one host
  • changed add-context to add-context1 and add-context2 -> you need unique names and not reuse it between services
  • use PathPrefix instead of Path, so we can match /app1 and /app1/
  • easier regex: ^(.+/app1)$$ will match anything with ends with /app1
  • use image traefik/whoami:v1.8.7 instead of nginx only for easier testing

Try also to look at servername:8080 to see the traefik dashboard. There I could see that there was a problem using add-context in both services.
To evaluate regexes you can use https://regexr.com/

@y0nei
Copy link

y0nei commented Oct 7, 2023

anyone got it working for v3? It does an infinite redirect loop

@N-B-H
Copy link

N-B-H commented Mar 10, 2024

anyone got it working for v3? It does an infinite redirect loop

I had the same issue and spent pretty much a whole day to figure it out.
Simple redirect from my root directory to /de (or other subfolder whatsoever)

Valid for my-domain.com and www.my-domain.com, http and https.

For me, in my environment, self-hosted on coolify, this worked:


traefik.http.middlewares.redirect-to-de.redirectregex.regex=^(https?://(?:www\.)?my-domain\.com)/?$$
traefik.http.middlewares.redirect-to-de.redirectregex.replacement=$${1}/de

and the redirect-to-de middleware has to be added to each router. The full config is in my case:

traefik.enable=true
traefik.http.middlewares.gzip.compress=true
traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https
traefik.http.middlewares.redirect-to-de.redirectregex.regex=^(https?://(?:www\.)?my-domain\.com)/?$$
traefik.http.middlewares.redirect-to-de.redirectregex.replacement=$${1}/de
traefik.http.middlewares.redirect-to-de.redirectregex.permanent=true
traefik.http.routers.http-0-mycontainer.entryPoints=http
traefik.http.routers.http-0-mycontainer.middlewares=redirect-to-https,redirect-to-de
traefik.http.routers.http-0-mycontainer.rule=Host(`my-domain.com`) && PathPrefix(`/`)
traefik.http.routers.http-0-mycontainer.service=http-0-mycontainer
traefik.http.routers.http-1-mycontainer.entryPoints=http
traefik.http.routers.http-1-mycontainer.middlewares=redirect-to-https,redirect-to-de
traefik.http.routers.http-1-mycontainer.rule=Host(`www.my-domain.com`) && PathPrefix(`/`)
traefik.http.routers.http-1-mycontainer.service=http-1-mycontainer
traefik.http.routers.https-0-mycontainer.entryPoints=https
traefik.http.routers.https-0-mycontainer.middlewares=gzip,redirect-to-de
traefik.http.routers.https-0-mycontainer.rule=Host(`my-domain.com`) && PathPrefix(`/`)
traefik.http.routers.https-0-mycontainer.service=https-0-mycontainer
traefik.http.routers.https-0-mycontainer.tls.certresolver=letsencrypt
traefik.http.routers.https-0-mycontainer.tls=true
traefik.http.routers.https-1-mycontainer.entryPoints=https
traefik.http.routers.https-1-mycontainer.middlewares=gzip,redirect-to-de
traefik.http.routers.https-1-mycontainer.rule=Host(`www.my-domain.com`) && PathPrefix(`/`)
traefik.http.routers.https-1-mycontainer.service=https-1-mycontainer
traefik.http.routers.https-1-mycontainer.tls.certresolver=letsencrypt
traefik.http.routers.https-1-mycontainer.tls=true
traefik.http.services.http-0-mycontainer.loadbalancer.server.port=80
traefik.http.services.http-1-mycontainer.loadbalancer.server.port=80
traefik.http.services.https-0-mycontainer.loadbalancer.server.port=80
traefik.http.services.https-1-mycontainer.loadbalancer.server.port=80


Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment