Traefik redirects https to http and trailing slash issue [traefik, nginx, gatsby]

Hey,

I didn't find a solution for my problem yet.

I use Traefik as a reverse proxy to route to multiple sites. One is an nginx server hosting a static Gatsby site. And I don't understand how to set it up properly. I actually don't care whether there is a trailing slash or not, I just want it to run without issues :wink: So assuming all pages need a trailing slash, I want the redirects to look as follows:

http://example.com/page -> http://example.com/page/
https://example.com/page -> https://example.com/page/
...

Here is my current setting:

Traefik: docker-compose.yml

version: '3.7'

services:
  traefik:
    image: traefik:v2.2.1
    container_name: traefik
    restart: unless-stopped
    security_opt:
      - no-new-privileges:true
    networks:
      - proxy
    ports:
      - 80:80
      - 443:443
    volumes:
      - /etc/localtime:/etc/localtime:ro
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./data/traefik.yml:/traefik.yml:ro
      - ./data/dynamic_conf.yml:/dynamic_conf.yml
      - ./data/acme.json:/acme.json
    environment:
      # Here are my API_KEY and API_SECRET
  labels:
      - "traefik.enable=true"

      # add trailing slash
      - "traefik.http.middlewares.add-trailing-slash.chain.middlewares=strip-prefix-1,strip-prefix-2"
      - "traefik.http.middlewares.strip-prefix-1.redirectregex.regex=^(https?://[^/]+/[a-z0-9_]+)$$"
      - "traefik.http.middlewares.strip-prefix-1.redirectregex.replacement=$${1}/"
      - "traefik.http.middlewares.strip-prefix-1.redirectregex.permanent=true"
      - "traefik.http.middlewares.strip-prefix-2.stripprefixregex.regex=/[a-z0-9_]+"

      # remove trailing slash
      - "traefik.http.middlewares.remove-trailing-slash.chain.middlewares=strip-prefix-3,strip-prefix-4"
      - "traefik.http.middlewares.strip-prefix-3.redirectregex.regex=^(https?://[^/]+/[a-z0-9_]+)/$$"
      - "traefik.http.middlewares.strip-prefix-3.redirectregex.replacement=$${1}"
      - "traefik.http.middlewares.strip-prefix-3.redirectregex.permanent=true"
      - "traefik.http.middlewares.strip-prefix-4.stripprefixregex.regex=/[a-z0-9_]+"

      # global wildcard certificates for our godaddy domains
      - "traefik.http.routers.wildcard-certs.tls.certresolver=godaddy"
      - "traefik.http.routers.wildcard-certs.tls.domains[0].main=example.com"
      - "traefik.http.routers.wildcard-certs.tls.domains[0].sans=*.example.com"

networks:
  proxy:
    external: true

traefik.yml

api:
  dashboard: true
entryPoints:
  http:
    address: ":80"
    #compress: false
    http:
      redirections:
        entryPoint:
          to: https
          scheme: https
          permanent: true
  https:
    address: ":443"
providers:
  docker:
    endpoint: "unix:///var/run/docker.sock"
    exposedByDefault: false
  file:
    filename: "/dynamic_conf.yml"
certificatesResolvers:
  ...

nginx: docker-compose.yml

version: '3.7'

services:
  nginx:
    image: nginx:latest
    restart: always
    volumes:
      - ./web-data/public:/usr/share/nginx/html/
      - ./nginx/gatsby-nginx.conf:/etc/nginx/conf.d/default.conf

    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.nginx-secure.entrypoints=https"
      - "traefik.http.routers.nginx-secure.rule=Host(`example.com`, `www.example.com`)"
      - "traefik.http.routers.nginx-secure.tls=true"
      - "traefik.docker.network=proxy"
      #- "traefik.http.routers.nginx-secure.middlewares=add-trailing-slash"

    networks:
      - proxy
      - default
   
networks:
  proxy:
    external: true

And finally my nginx default.conf looks as follows:

server {
    listen 80; #443 ssl http2;

    server_name example.com;

    root /usr/share/nginx/html;
    index index.html;

    autoindex off;
    charset utf-8;

    error_page 404 /404.html;

    location ~* \.(?:html)$ {
        add_header Cache-Control "no-store";
        expires    off;
    }

    location /page-data {
        add_header Cache-Control "public, max-age=0, must-revalidate";
    }

    location = /sw.js {
        add_header Cache-Control "public, max-age=0, must-revalidate";
    }

    location /static {
        add_header Cache-Control "public, max-age=31536000, immutable";
    }

    location ~* \.(?:js|css)$ {
        add_header Cache-Control "public, max-age=31536000, immutable";
    }

    #rewrite ^([^.\?]*[^/])$ $1/ permanent;
    #try_files \$uri \$uri/ \$uri/index.html =404
    #try_files \$uri \$uri/index.html =404
    try_files $uri $uri/ $uri/index.html =404;
}

So this configuration actually works. I can browse all sites, but there is one issue. Traefik redirects https to http if there is no trailing slash. For instance:

ben@happiness:~/public$ curl -I https://example.com/contact
HTTP/1.1 301 Moved Permanently
Content-Length: 169
Content-Type: text/html
Date: Sat, 08 Aug 2020 09:36:25 GMT
Location: http://example.com/contact/
Server: nginx/1.19.1

Which in turn redirects to the final destination:

ben@happiness:~/public$ curl -I http://example.com/contact/
HTTP/1.1 308 Permanent Redirect
Location: https://example.com/contact/
Date: Sat, 08 Aug 2020 09:37:33 GMT
Content-Length: 18
Content-Type: text/plain; charset=utf-8

Not only that it is ugly to redirect https to http and probably insecure, but also my Mautic form doesn't work properly (i.e., doesn't redirect to the thank-you page and doesn't show if a mandatory field has not been filled).

So I played with adding a trailing slash (cf. the two docker-compose.yml). Specifically, if I uncomment in my nginx docker-compose.yml the following line:

- "traefik.http.routers.nginx-secure.middlewares=add-trailing-slash"

With forced trailing slashes redirects look okay:

ben@happiness:~/public$ curl -I https://example.com/contact
HTTP/1.1 308 Permanent Redirect
Location: https://example.com/contact/
Date: Sat, 08 Aug 2020 09:42:20 GMT
Content-Length: 18
Content-Type: text/plain; charset=utf-8

Yet, I don't know how to setup nginx in this case. In particular, nginx runs into several 404s when accessing the site:

VM684:1 GET https://example.com/page-data/app-data.json 404
(anonymous) @ VM684:1
(anonymous) @ app-fd0fd20afca3d57009ab.js:1
C @ app-fd0fd20afca3d57009ab.js:1
t.memoizedGet @ app-fd0fd20afca3d57009ab.js:1
t.loadAppData @ app-fd0fd20afca3d57009ab.js:1
t.loadPage @ app-fd0fd20afca3d57009ab.js:1
loadPage @ app-fd0fd20afca3d57009ab.js:1
(anonymous) @ app-fd0fd20afca3d57009ab.js:1
Promise.then (async)
UxWs @ app-fd0fd20afca3d57009ab.js:1
a @ webpack-runtime-703898b324de4f9dc223.js:1
t @ webpack-runtime-703898b324de4f9dc223.js:1
r @ webpack-runtime-703898b324de4f9dc223.js:1
(anonymous) @ app-fd0fd20afca3d57009ab.js:1
VM684:1 GET https://example.com/page-data/index/page-data.json 404
(anonymous) @ VM684:1
(anonymous) @ app-fd0fd20afca3d57009ab.js:1
C @ app-fd0fd20afca3d57009ab.js:1
t.memoizedGet @ app-fd0fd20afca3d57009ab.js:1
t.fetchPageDataJson @ app-fd0fd20afca3d57009ab.js:1
t.loadPageDataJson @ app-fd0fd20afca3d57009ab.js:1
r.loadPageDataJson @ app-fd0fd20afca3d57009ab.js:1
t.loadPage @ app-fd0fd20afca3d57009ab.js:1
loadPage @ app-fd0fd20afca3d57009ab.js:1
(anonymous) @ app-fd0fd20afca3d57009ab.js:1
Promise.then (async)
UxWs @ app-fd0fd20afca3d57009ab.js:1
a @ webpack-runtime-703898b324de4f9dc223.js:1
t @ webpack-runtime-703898b324de4f9dc223.js:1
r @ webpack-runtime-703898b324de4f9dc223.js:1
(anonymous) @ app-fd0fd20afca3d57009ab.js:1
VM684:1 GET https://example.com/page-data/app-data.json 404
(anonymous) @ VM684:1
(anonymous) @ app-fd0fd20afca3d57009ab.js:1
C @ app-fd0fd20afca3d57009ab.js:1
t.memoizedGet @ app-fd0fd20afca3d57009ab.js:1
t.loadAppData @ app-fd0fd20afca3d57009ab.js:1
(anonymous) @ app-fd0fd20afca3d57009ab.js:1
Promise.then (async)
t.loadAppData @ app-fd0fd20afca3d57009ab.js:1
t.loadPage @ app-fd0fd20afca3d57009ab.js:1
loadPage @ app-fd0fd20afca3d57009ab.js:1
(anonymous) @ app-fd0fd20afca3d57009ab.js:1
Promise.then (async)
UxWs @ app-fd0fd20afca3d57009ab.js:1
a @ webpack-runtime-703898b324de4f9dc223.js:1
t @ webpack-runtime-703898b324de4f9dc223.js:1
r @ webpack-runtime-703898b324de4f9dc223.js:1
(anonymous) @ app-fd0fd20afca3d57009ab.js:1
VM684:1 GET https://example.com/page-data/404.html/page-data.json 404
(anonymous) @ VM684:1
(anonymous) @ app-fd0fd20afca3d57009ab.js:1
C @ app-fd0fd20afca3d57009ab.js:1
t.memoizedGet @ app-fd0fd20afca3d57009ab.js:1
t.fetchPageDataJson @ app-fd0fd20afca3d57009ab.js:1
(anonymous) @ app-fd0fd20afca3d57009ab.js:1
Promise.then (async)
t.fetchPageDataJson @ app-fd0fd20afca3d57009ab.js:1
t.loadPageDataJson @ app-fd0fd20afca3d57009ab.js:1
r.loadPageDataJson @ app-fd0fd20afca3d57009ab.js:1
t.loadPage @ app-fd0fd20afca3d57009ab.js:1
loadPage @ app-fd0fd20afca3d57009ab.js:1
(anonymous) @ app-fd0fd20afca3d57009ab.js:1
Promise.then (async)
UxWs @ app-fd0fd20afca3d57009ab.js:1
a @ webpack-runtime-703898b324de4f9dc223.js:1
t @ webpack-runtime-703898b324de4f9dc223.js:1
r @ webpack-runtime-703898b324de4f9dc223.js:1
(anonymous) @ app-fd0fd20afca3d57009ab.js:1
VM684:1 GET https://example.com/page-data/app-data.json 404
(anonymous) @ VM684:1
(anonymous) @ app-fd0fd20afca3d57009ab.js:1
C @ app-fd0fd20afca3d57009ab.js:1
t.memoizedGet @ app-fd0fd20afca3d57009ab.js:1
t.loadAppData @ app-fd0fd20afca3d57009ab.js:1
(anonymous) @ app-fd0fd20afca3d57009ab.js:1
Promise.then (async)
t.loadAppData @ app-fd0fd20afca3d57009ab.js:1
(anonymous) @ app-fd0fd20afca3d57009ab.js:1
Promise.then (async)
t.loadAppData @ app-fd0fd20afca3d57009ab.js:1
t.loadPage @ app-fd0fd20afca3d57009ab.js:1
loadPage @ app-fd0fd20afca3d57009ab.js:1
(anonymous) @ app-fd0fd20afca3d57009ab.js:1
Promise.then (async)
UxWs @ app-fd0fd20afca3d57009ab.js:1
a @ webpack-runtime-703898b324de4f9dc223.js:1
t @ webpack-runtime-703898b324de4f9dc223.js:1
r @ webpack-runtime-703898b324de4f9dc223.js:1
(anonymous) @ app-fd0fd20afca3d57009ab.js:1
VM684:1 GET https://example.com/page-data/app-data.json 404
(anonymous) @ VM684:1
(anonymous) @ app-fd0fd20afca3d57009ab.js:1
C @ app-fd0fd20afca3d57009ab.js:1
t.memoizedGet @ app-fd0fd20afca3d57009ab.js:1
t.loadAppData @ app-fd0fd20afca3d57009ab.js:1
(anonymous) @ app-fd0fd20afca3d57009ab.js:1
Promise.then (async)
t.loadAppData @ app-fd0fd20afca3d57009ab.js:1
(anonymous) @ app-fd0fd20afca3d57009ab.js:1
Promise.then (async)
t.loadAppData @ app-fd0fd20afca3d57009ab.js:1
(anonymous) @ app-fd0fd20afca3d57009ab.js:1
Promise.then (async)
t.loadAppData @ app-fd0fd20afca3d57009ab.js:1
t.loadPage @ app-fd0fd20afca3d57009ab.js:1
loadPage @ app-fd0fd20afca3d57009ab.js:1
(anonymous) @ app-fd0fd20afca3d57009ab.js:1
Promise.then (async)
UxWs @ app-fd0fd20afca3d57009ab.js:1
a @ webpack-runtime-703898b324de4f9dc223.js:1
t @ webpack-runtime-703898b324de4f9dc223.js:1
r @ webpack-runtime-703898b324de4f9dc223.js:1
(anonymous) @ app-fd0fd20afca3d57009ab.js:1
app-fd0fd20afca3d57009ab.js:1 Uncaught (in promise) Error: page resources for / not found. Not rendering React
    at app-fd0fd20afca3d57009ab.js:1
(anonymous) @ app-fd0fd20afca3d57009ab.js:1
Promise.then (async)
(anonymous) @ app-fd0fd20afca3d57009ab.js:1
Promise.then (async)
UxWs @ app-fd0fd20afca3d57009ab.js:1
a @ webpack-runtime-703898b324de4f9dc223.js:1
t @ webpack-runtime-703898b324de4f9dc223.js:1
r @ webpack-runtime-703898b324de4f9dc223.js:1
(anonymous) @ app-fd0fd20afca3d57009ab.js:1
manifest.json:1 GET https://example.com/icons-2282555dbdb424e391e15f39684cacbd/manifest.json 404
manifest.json:1 Manifest: Line: 1, column: 1, Syntax error.

I highly appreciate any pointer on how to fix these issues!

It would also be great to learn how to setup http2 in this scenario!

Thanks!
Ben

1 Like

Did you solve the problem?

Unfortunately, no :confused:

I solved my case adding redirect http to https on Traefik config:

            - "--entrypoints.web.address=:80"
            - "--entrypoints.web.http.redirections.entryPoint.to=websecure"
            - "--entrypoints.web.http.redirections.entryPoint.scheme=https"
            - "--entrypoints.websecure.address=:443"

My reference was: https://doc.traefik.io/traefik/routing/entrypoints/#http-options

Thanks, this is what I'm already doing in the traefik.yml

entryPoints:
  http:
    address: ":80"
    #compress: false
    http:
      redirections:
        entryPoint:
          to: https
          scheme: https
          permanent: true
  https:
    address: ":443"

Unfortunately, the issue still persists...

Problem has been resolved in nginx.conf by adding:

rewrite ^([^.]*[^/])$ https://example.com$1/ permanent; 
1 Like