Docker Compose Traefik + HA causes websocket 404

I’ve been working though the forum + docs to set up Home Assistant in Docker and make it publicly available. My home lab currently uses tailscale + traefik to set up a FQDN and TLS for certs. This solution works fine for other services but for HA when I navigate to my domain root, e.g. machine.tailnet.net it will load the auth page and after a successful auth will redirect to:

https://machine.tailnet.net/lovelace?auth_callback=1&code=XXXXXXXXXXX&storeToken=true

and show error:
Unable to connect to Home Assistant. Retrying in XX seconds…

Under the hood in dev tools I can see the hassTokens write to session storage. And the network tab shows a 404 error under the websocket path wss://machine.tailnet.net/api/websocket.

I created an additional issue since this differs slightly from Websockets Error 500 in traefik logs

Attached are my docker-compose files, 1x traefik + tailscale and 1x for home-assistant.

docker-compose-ts-traefik
networks:
  sbridge:
    name: sbridge
    external: true
    driver_opts:
      com.docker.network.driver.mtu: 1280
    attachable: true

services:

  traefik:
    #e.g., image: "traefik:v3.0", "traefik:3.0.0-beta1", "traefik:3.0.0-beta2", "traefik:3.0.0-beta3", "traefik:v3.0.3", "traefik:v3.1.4"
    image: "traefik:3.2.3"
    container_name: "traefik"
    privileged: true
    command:
      - "--log.level=DEBUG"
      - "--api.insecure=true"
      - "--serversTransport.insecureSkipVerify=true"
      - "--providers.docker=true"
      - "--providers.docker.network=sbridge"
      - "--providers.docker.exposedbydefault=false"
      - "--providers.docker.watch=true"
      - "--accesslog=true"
      - "--accesslog.filepath=/var/log/traefik/access.log"
      - "--entrypoints.web.address=:80"
      - "--entrypoints.websecure.address=:443"
      - "--entrypoints.has.address=:8123"
      - "--entryPoints.has.http.tls=true"
      - "--entrypoints.file.address=:888"
      - "--entryPoints.file.http.tls=true"
      - "--entryPoints.websecure.http.tls=true"
      - "--entrypoints.web.http.redirections.entryPoint.to=websecure"
      - "--entrypoints.web.http.redirections.entryPoint.scheme=https"
      - "--entryPoints.websecure.forwardedHeaders.trustedIPs=127.0.0.1/32,10.0.10.0/24,172.0.0.0/16,172.18.0.0/24,192.168.65.1,172.18.0.9"
      - "--entryPoints.web.forwardedHeaders.trustedIPs=127.0.0.1/32,10.0.10.0/24,172.0.0.0/16,172.18.0.0/24,192.168.65.1,172.18.0.9"
      - "--providers.file.watch=true"
      - "--providers.file.directory=/etc/traefik/dynamic"
    ports:
      - "80:80"
      - "443:443"
      - "8080:8080"
    volumes:
      - ${CONFIG}/traefik/config/config.yml:/config.yml
      - ${CONFIG}/traefik/certs-traefik.yaml:/etc/traefik/dynamic/certs-traefik.yaml
      - ${CONFIG}/tailscale/varlib/certs:/etc/certs
      - /var/run/docker.sock:/var/run/docker.sock:ro
    labels:
      - 'traefik.enable=true'
      - 'traefik.http.routers.mydashboard.entrypoints=web,websecure'
      - 'traefik.http.routers.mydashboard.rule=(Host(`${DOMAIN}`) && PathPrefix(`/proxy`) || PathPrefix(`/api`))'
      - "traefik.http.routers.mydasboard.tls=true"
      - 'traefik.http.routers.mydashboard.service=api@internal'
   
      - 'traefik.http.middlewares.cors.headers.accesscontrolallowcredentials=true'
      - "traefik.http.middlewares.cors.headers.accesscontrolallowmethods=GET,OPTIONS,PUT,POST,DELETE"
      - 'traefik.http.middlewares.cors.headers.accesscontrolallowheaders=*'
      - 'traefik.http.middlewares.cors.headers.customResponseHeaders.Access-Control-Allow-Origin=${DOMAIN}'
      - "traefik.http.middlewares.cors.headers.addvaryheader=true"
      - "traefik.http.middlewares.cors.headers.accesscontrolmaxage=100"
      - "traefik.http.middlewares.dashboard-strip.stripprefix.prefixes=/proxy"
      - "traefik.http.routers.mydashboard.middlewares=cors,dashboard-strip"
    networks:
      - sbridge
    depends_on:
      - tailscale

  tailscale:
    container_name: tailscale
    image: tailscale/tailscale:latest
    hostname: machine
    privileged: true
    network_mode: host
    cap_add:
      - NET_ADMIN
      - NET_RAW
      - SYS_MODULE
    volumes:
      - ${CONFIG}/tailscale/varlib:/var/lib
      - /tmp/tailscale/tmp:/tmp
      - /tmp/var/run/tailscale/tailscaled.sock:/var/run/tailscale/tailscaled.sock
      - /dev/net/tun:/dev/net/tun
    environment:
      - TS_STATE_DIR=/var/lib/tailscale
      - TS_AUTH_KEY=${TS_AUTH}
      - TS_SOCKET="tmp/var/run/tailscale/tailscaled.sock"
      - TS_ROUTES=172.18.0.0/24,10.0.10.0/24
      - TS_EXTRA_ARGS=--accept-routes
docker-compose-home-assistant
networks:
  sbridge:
    name: sbridge
    external: true
services:
  homeassistant:
    image: homeassistant/home-assistant:stable
    container_name: homeassistant
    # network_mode: host
    networks:
      - sbridge
    expose:
      - 8123
    ports:
      - 8123:8123 #optional
    environment:
      - PGID
      - PUID
      - TZ
    volumes:
      - ${CONFIG}/homeassistant/data:/config
    # devices:
    #   - /path/to/device:/path/to/device #optional
    restart: unless-stopped
    labels:
      - 'traefik.enable=true'
      - "traefik.docker.network=sbridge"
      - 'traefik.http.routers.homeassistant.service=homeassistant'
      - 'traefik.http.routers.homeassistant.rule=(Host(`${DOMAIN}`))'
      - "traefik.http.services.homeassistant.loadbalancer.server.port=8123"
      - 'traefik.http.routers.homeassistant.entrypoints=web,websecure'
      - 'traefik.http.routers.homeassistant.tls=true'
      - "traefik.http.routers.homeassistant.tls.domains[0].main=machine.tailnet.net"
      - "traefik.http.services.homeassistant.loadbalancer.passhostheader=true"
      - "traefik.http.middlewares.sslheader.headers.customrequestheaders.X-Forwarded-Proto=wss"
      - "traefik.http.middlewares.sslheader.headers.customRequestHeaders.X-Forwarded-Host=172.18.0.19:8123"
      - "traefik.http.routers.homeassistant.middlewares=sslheader"

The only thing that directly stands out to me, are your customRequestHeaders for websockets. I don’t have them in my config. What happens if you leave those out?

Edit: Oh, and you have two entrypoints for your homeassistant router. That looks odd to me too. but perhaps that works? :man_shrugging:

:rofl: honestly, I think those are left over from my throw spaghetti at the wall phase. I’ll take them out and post my results.

Sadly issue persists. I didn’t say it in the original post but similar to the other issue I linked to, visiting http://localhost:8123 serves the HA login & dashboard without issue sans SSL. Throwing in a screenshot of the full report I get of the websocket error in the network pane.

I have Home Assistant running on a Yellow and Traefik running on different machine, so I don’t use Docker labels for the configuration of my router, but this is how I have set it up using yaml:

http:
  routers:
    homeassistant-tls:
      tls:
        certresolver: transip
      entryPoints:
        - websecure
      service: homeassistant
      rule: Host(`my.remote.url`)

My service ‘homeassistant’ points to a URL, that is not relevant for you. My guess is that your http.services label is fine. Although I see a mix of " and ’ in your config. You might want to standardise on only one type. Or leave them out of your docker-compose file, I don’t use them for my labels on other containers.

Rewriting my config to labels is pretty straight forward. Perhaps you could try and see if this works?

You were definitely on to something with my Traefik config being a mix of docker labels and config files. I decided to take down docker and experiment with using Caddy + Tailscale to reverse proxy only HA.

I’m happy to report it works flawlessly now! Traefik definitely over complicated my setup and Caddy’s log output is far more clear and concise. I’m adding my docker-compose service setup for Caddy along with an example Caddy file in case it helps anyone in the future.

caddy-service

  caddy:
    image: caddy:latest
    container_name: caddy
    restart: unless-stopped
    networks:
      - sbridge
    depends_on:
      - tailscale
    ports:
      - 80:80
      - 443:443
    expose:
      - 2019
    volumes:
      - ${CONFIG}/caddy/Caddyfile:/etc/caddy/Caddyfile
      - ${CONFIG}/caddy/caddy_data:/data
      - ${CONFIG}/caddy/caddy_config:/config
      - ${CONFIG}/tailscale/socket:/var/run/tailscale
Caddyfile
{
	admin 0.0.0.0:2019 {
	}
}
machine.tailnet.net {
	reverse_proxy 10.0.10.2:8123
}