TLS Termination requests work, but PUT responses trigger "blocked loading mixed active content"

I have a python web app, a simple REST API implementing a blog CMS with web page dressing via html/css/js. (Note, use of bold below is to aid scanning. I'm not yelling.)

This is running as a docker container with a companion postgres database container at Digital Ocean. I've placed Traefik in front, Traefik handling TLS Termination to my web app.

The problem I am trying to solve is triggered by JavaScript use of the fetch() API only when performing PUT and DELETE requests.

JavaScript fetch() requests using GET or POST work fine, presumably sending https that Traefik decodes to http for the web app, and then the generated http API responses must be getting encoded as https for delivery to the web page.

The error is (I think) the JavaScript fetch() requests using PUT or DELETE occur in https while the response from the web API return as http, NOT getting encoded to https - hence the mixed active content browser error and the response being blocked.

My configuration uses a traefik.toml and a traefik_dynamic.toml files for Traefik's config. If anyone sees anything that ought to be changed, so the API's PUT & DELETE responses get encoded, so the https client does not complain about mixed active content, that'd be great.

traefik.toml file contents:

[entryPoints]
  [entryPoints.web]
    address = ":80"
    [entryPoints.web.http.redirections.entryPoint]
      to = "websecure"
      scheme = "https"

  [entryPoints.websecure]
    address = ":443"

[api]
  dashboard = true

[certificatesResolvers.lets-encrypt.acme]
  email = "blake@blakesenftner.com"
  storage = "acme.json"
  [certificatesResolvers.lets-encrypt.acme.tlsChallenge]

[providers.docker]
  watch = true
  network = "web"

[providers.file]
  filename = "traefik_dynamic.toml"

traefik_dynamic.toml file contents:

[http.middlewares.simpleAuth.basicAuth]
  users = [
    "admin:$apr1$nzY1.zpG$xBsroABcqsyYj7EqmYM/10"
  ]

[http.routers.api]
  rule = "Host(`monitor.blakesenftner.com`)"
  entrypoints = ["websecure"]
  middlewares = ["simpleAuth"]
  service = "api@internal"
  [http.routers.api.tls]
    certResolver = "lets-encrypt"

I then launch Traefik with this command line:

docker run -d \
      -v /var/run/docker.sock:/var/run/docker.sock \
      -v $PWD/traefik.toml:/traefik.toml \
      -v $PWD/traefik_dynamic.toml:/traefik_dynamic.toml \
      -v $PWD/acme.json:/acme.json \
      -p 80:80 \
      -p 443:443 \
      --network web \
      --name traefik \
      traefik:v2.2

and finally, this is the docker-compose file that launches the web app:

version: '3.8'

networks:
  web:
    external: true
  internal:
    external: false

services:
  blog:
    build: ./src
    command: |
      bash -c 'while !</dev/tcp/db/5432; do sleep 1; done; uvicorn app.main:app --reload --workers 1 --host 0.0.0.0 --port 8000'
    volumes:
      - ./src/:/usr/src/app/
    ports:
      - 8000:8000
    environment:
      - DATABASE_URL=postgresql://basic_blog:basic_blog@db/basic_blog_dev
    labels:
      # next line is unecessary because the lines after it default to traefik.enable=true
      # - traefik.enable=true
      - traefik.http.routers.blog.rule=Host(`blog.blakesenftner.com`)
      - traefik.http.routers.blog.tls=true
      - traefik.http.routers.blog.tls.certresolver=lets-encrypt
      - traefik.port=80
      # middleware redirect
      # - "traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https"
      # global redirect to https
      # - "traefik.http.routers.redirs.rule=hostregexp(`{host:.+}`)"
      # - "traefik.http.routers.redirs.entrypoints=web"
      # - "traefik.http.routers.redirs.middlewares=redirect-to-https"
    networks:
      - internal
      - web

  db:
    image: postgres:13-alpine
    volumes:
      - postgres_data:/var/lib/postgresql/data/
    expose:
      - 5432
    environment:
      - POSTGRES_USER=basic_blog
      - POSTGRES_PASSWORD=basic_blog
      - POSTGRES_DB=basic_blog_dev
    networks:
      - internal
    labels:
      - traefik.enable=false

volumes:
  postgres_data:

This is usually rather a problem of the app. Use your browser's developer tools' network tab to see what is happening. Also enable Traefik access log to see requests to Traefik.

Two common problems:

  1. app redirects after POST, PUT or DELETE to a URL with non-secure protocol
  2. app response is okay, but it includes links/images/scripts with non-secure protocol which are then used/included by Javascript

Example Wordpress: you need to configure its base URL (with protocol). If it is set to http, then you will have issues using https URLs.

I highly recommend to set exposedByDefault to false to improve security and only enable needed services with label traefik.enable=true. Check docs.

Furthermore, this seems invalid. If you need to set the port, check docs.

Thank you! The issue was in my code after all.

Future readers, if you're working in Python and FastAPI: when defining your route paths, do not include a trailing slash. That trailing slash triggers an undesired redirect. In my case, to an insecure path.