Error in putting Home Assistant behind Traefik

Hi there,
I'm trying to put Home Assistant docker behind Traefik so that I can reach it from the outside. But I'm getting into troubles... it returns me a 404 error, page not found and I don't understand why...

here it is its File Provider:

http:
  routers:
    home-assistant-rtr:
      rule: "Host(`hass.{{env "DOMAINNAME_1"}}`)"
      entryPoints:
        - websecure
        middlewares:
        - chain-no-auth@file
      service: home-assistant-svc
      tls:
        certResolver: dns-cloudflare
        options: tls-opts@file
  services:
    home-assistant-svc:
      loadBalancer:
        servers:
          - url: "http://{{env "SERVER_LAN_IP"}}:8123" # or HTTP://hostname.local:8123

The Home Assistant docker compose file is pretty plain:

services:
  home-assistant:
    container_name: home-assistant
    image: ghcr.io/home-assistant/home-assistant:stable
      #depends_on:
      #- mosquitto
    security_opt:
      - no-new-privileges:true
    healthcheck:
      test: ["CMD", "curl", "-s", "http://localhost:8123"]
      interval: 5m
      timeout: 3s
      retries: 10
    restart: unless-stopped
    network_mode: host
    #networks: 
    #  - socket_proxy
    environment:
    #  - DOCKER_HOST=tcp://socket-proxy:2375
      - TZ=$TZ
    #devices:
    #  - /dev/ttyAMA0
    #  - /dev/vcio
    volumes:
      - $DOCKERDIR/appdata/homeassistant/config:/config
      - /run/dbus:/run/dbus:ro
# container put behind Traefik's network by the way of File Provider method, with a YAML file under "rules" folder

nothing fancy here...

and my Traefik docker compose file is the following:

services:
  # Traefik 3 - Reverse Proxy
  traefik:
    container_name: traefik
    image: traefik:latest
    security_opt:
      - no-new-privileges:true
    restart: unless-stopped
    networks:
      t3_proxy:
        ipv4_address: 192.xxx.xxx.254 # You can specify a static IP
      socket_proxy:
    command: # CLI arguments
      - --global.checkNewVersion=false
      - --global.sendAnonymousUsage=true
      - --entrypoints.web.address=:80
      - --entrypoints.websecure.address=:443
      - --entrypoints.traefik.address=:8080
      - --entrypoints.websecure.http.tls=true
      - --entrypoints.web.http.redirections.entrypoint.to=websecure
      - --entrypoints.web.http.redirections.entrypoint.scheme=https
      - --entrypoints.web.http.redirections.entrypoint.permanent=true
      - --api=true
      - --api.dashboard=true
      - --api.insecure=true
      #- --serversTransport.insecureSkipVerify=true
      # Allow these IPs to set the X-Forwarded-* headers - Cloudflare IPs: https://www.cloudflare.com/ips/
      #- --entrypoints.websecure.forwardedHeaders.trustedIPs=$CLOUDFLARE_IPS,$LOCAL_IPS
      - --log=true
      - --log.filePath=/logs/traefik.log
      - --log.level=ERROR # (Default: error) DEBUG, INFO, WARN, ERROR, FATAL, PANIC
      - --accessLog=true
      - --accessLog.filePath=/logs/access.log
      - --accessLog.bufferingSize=100 # Configuring a buffer of 100 lines
      - --accessLog.filters.statusCodes=204-299,400-499,500-599
      - --providers.docker=true
      # - --providers.docker.endpoint=unix:///var/run/docker.sock # Disable for Socket Proxy. Enable otherwise.
      - --providers.docker.endpoint=tcp://socket-proxy:2375 # Enable for Socket Proxy. Disable otherwise.
      - --providers.docker.exposedByDefault=false
      - --providers.docker.network=t3_proxy 
      # - --providers.docker.swarmMode=false # Traefik v2 Swarm
      # - --providers.swarm.endpoint=tcp://127.0.0.1:2377 # Traefik v3 Swarm
      - --entrypoints.websecure.http.tls.options=tls-opts@file
      # Add dns-cloudflare as default certresolver for all services. Also enables TLS and no need to specify on individual services
      - --entrypoints.websecure.http.tls.certresolver=dns-cloudflare
      - --entrypoints.websecure.http.tls.domains[0].main=$DOMAINNAME_1
      - --entrypoints.websecure.http.tls.domains[0].sans=*.$DOMAINNAME_1
      # - --entrypoints.websecure.http.tls.domains[1].main=$DOMAINNAME_2 # Pulls main cert for second domain
      # - --entrypoints.websecure.http.tls.domains[1].sans=*.$DOMAINNAME_2 # Pulls wildcard cert for second domain
      - --providers.file.directory=/rules # Load dynamic configuration from one or more .toml or .yml files in a directory
      - --providers.file.watch=true # Only works on top level files in the rules folder
      # - --certificatesResolvers.dns-cloudflare.acme.caServer=https://acme-staging-v02.api.letsencrypt.org/directory # LetsEncrypt Staging Server - uncomment when testing
      - --certificatesResolvers.dns-cloudflare.acme.storage=/acme.json
      - --certificatesResolvers.dns-cloudflare.acme.dnsChallenge.provider=cloudflare
      - --certificatesResolvers.dns-cloudflare.acme.dnsChallenge.resolvers=1.1.1.1:53,1.0.0.1:53
      - --certificatesResolvers.dns-cloudflare.acme.dnsChallenge.delayBeforeCheck=90 # To delay DNS check and reduce LE hitrate
    ports:
      - target: 80
        published: 80
        protocol: tcp
        mode: host
      - target: 443
        published: 443
        protocol: tcp
        mode: host
    # Enable these following 4 lines to have Traefik reachable from the inside in plain http mode
      - target: 8080 # need to enable --api.insecure=true
        published: 8080
        protocol: tcp
        mode: host
    volumes:
      - $DOCKERDIR/appdata/traefik3/rules/$HOSTNAME:/rules # Dynamic File Provider directory
      # - /var/run/docker.sock:/var/run/docker.sock:ro # Enable if not using Socket Proxy
      - $DOCKERDIR/appdata/traefik3/acme/acme.json:/acme.json # Certs File 
      - $DOCKERDIR/logs/$HOSTNAME/traefik:/logs # Traefik logs
    environment:
      - TZ=$TZ
      - CF_DNS_API_TOKEN_FILE=/run/secrets/cf_dns_api_token    
      - HTPASSWD_FILE=/run/secrets/basic_auth_credentials # HTTP Basic Auth Credentials
      - DOMAINNAME_1 # Passing the domain name to traefik container to be able to use the variable in rules. 
    secrets:
      - cf_dns_api_token
      - basic_auth_credentials
    labels:
      - "traefik.enable=true"
      # HTTP Routers
      - "traefik.http.routers.traefik-rtr.entrypoints=websecure"
      - "traefik.http.routers.traefik-rtr.rule=Host(`traefik.$DOMAINNAME_1`)"
      # Services - API
      - "traefik.http.routers.traefik-rtr.service=api@internal"
      # Middlewares
      - "traefik.http.routers.traefik-rtr.middlewares=chain-basic-auth@file" # For Basic HTTP Authentication

someone has any hint on the reason why I got a 404 with HA? Thanks in advance!

Enable and check Traefik debug log (doc) and also Traefik access log in JSON format (doc).

Is the dynamic config file read, is the router created, what does the access log tell you during a request?

I'm facing issues even with Traefik itself, please see this post, there you can see the logs.

At the moment I cannot see any dynamic Provider file correctly loaded, in fact I cannot see neither Portainer nor Home-Assistant any more. It's like Traefik cannot read the entire folder anymore... so weird... :frowning:
because I cannot see those services listed in the dashboard:

as you can see there are only 4 services ...

First step would be to check the dynamic config files. Go into Traefik Docker container and check if files are there, are readable and have the expected content:

docker exec -it traefik sh
cat /rules/*

yes, dynamic config files are there, are readable and have the expected contents.

http:
  routers:
    home-assistant-rtr:
      rule: "Host(`hass.{{env "DOMAINNAME_1"}}`)"
      entryPoints:
        - websecure
      middlewares:
        - chain-no-auth@file
      service: home-assistant-svc
      tls:
        certResolver: dns-cloudflare
  services:
    home-assistant-svc:
      loadBalancer:
        servers:
          - url: "http://{{env "SERVER_LAN_IP"}}:8123" # or HTTP://hostname.local:8123

http:
  routers:
    portainer-rtr:
      rule: "Host(`portainer.{{env "DOMAINNAME_1"}}`)" 
      entryPoints:
        - websecure
      middlewares:
        - chain-no-auth@file
      service: portainer-svc
      tls:
        certResolver: dns-cloudflare
        options: tls-opts@file
  services:
    portainer-svc:
      loadBalancer:
        servers:
          - url: "portainer:9000"
# "http://{{env "SERVER_LAN_IP"}}:9000" if Portainer is outside t3 proxy network
# Or, "http://portainer:9000" if Portainer is part of t3_proxy network

http:
  middlewares:
    chain-basic-auth:
      chain:
        middlewares:
          - middlewares-rate-limit
          - middlewares-secure-headers
          - middlewares-basic-auth
          - middlewares-compress
http:
  middlewares:
    chain-no-auth:
      chain:
        middlewares:
          #- middlewares-traefik-bouncer # leave this out if you are not using CrowdSec
          - middlewares-rate-limit
          - middlewares-secure-headers
http:
  middlewares:
    middlewares-basic-auth:
      basicAuth:
        # users:
        #   - "user:$Username"
        usersFile: "/run/secrets/basic_auth_credentials"
        realm: "Traefik 3 Basic Auth"

http:
  middlewares:
    middlewares-compress:
      compress: {}
http:
  middlewares:
    middlewares-rate-limit:
      rateLimit:
        average: 100
        burst: 50
http:
  middlewares:
    middlewares-secure-headers:
      headers:
        accessControlAllowMethods:
          - GET
          - OPTIONS
          - PUT
        accessControlMaxAge: 100
        hostsProxyHeaders:
          - "X-Forwarded-Host"
        stsSeconds: 63072000
        stsIncludeSubdomains: true
        stsPreload: true
        # forceSTSHeader: true # This is a good thing but it can be tricky. Enable after everything works.
        customFrameOptionsValue: SAMEORIGIN # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options
        contentTypeNosniff: true
        browserXssFilter: true
        referrerPolicy: "same-origin"
        permissionsPolicy: "camera=(), microphone=(), geolocation=(), payment=(), usb=(), vr=()"
        customResponseHeaders:
          X-Robots-Tag: "none,noarchive,nosnippet,notranslate,noimageindex," # disable search engines from indexing home server
          server: "" # hide server info from visitors
tls:
  options:
    tls-opts:
      minVersion: VersionTLS12
      cipherSuites:
        - TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
        - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
        - TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
        - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
        - TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305
        - TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305
        - TLS_AES_128_GCM_SHA256
        - TLS_AES_256_GCM_SHA384
        - TLS_CHACHA20_POLY1305_SHA256
        - TLS_FALLBACK_SCSV # Client is doing version fallback. See RFC 7507
      curvePreferences:
        - CurveP521
        - CurveP384
      sniStrict: true
/ # 


I've been partially successful after rewriting all the dynamic provider files, I have finally been able to have no errors in the dashboard:

and this is the log file: LOG LINK

but now when I try to reach those services from outside:

  • Traefik's dashboard is working correctly from the outside.
  • Portainer is not reachable :frowning: because I got an "Internal Server Error" page.
  • Home-Assistant (https://hass.sweet.home) is not reachable too, and I got an "Bad Gateway" error in browser window.

Maybe I missed it, but looks like you didn't include the SERVER_LAN_IP variable in your traefik's compose.yaml environment section. I see DOMAINNAME_1 only. You use this in your traefik config {{env "SERVER_LAN_IP"}} and I believe you need to pass them through to use in dynamic configuration files.

No, today I've finally found the solution:

in Ha configuration.yaml section:

http:
  use_x_forwarded_for: true
  trusted_proxies:
- 127.0.0.1
- SERVER_LAN_IP
- DOCKER **PROXY** subnet
- DOCKER subnet (docker 0)

with this modification everything seems to work properly!

1 Like