Help Needed: Traefik + Authelia Behind Cloudflare Tunnel Always Returns 401

Hello everyone,

I’ve been struggling to get Authelia’s forward-auth flow working behind a Cloudflare Tunnel and Traefik v3.0. Every request to my protected service immediately returns 401 Unauthorized instead of redirecting me to the Authelia login page. Below is a complete overview of my setup, with secrets redacted. Any pointers would be hugely appreciated!


1. Environment & Versions

  • Docker & Docker Compose: Docker Engine 24.x, Compose v2.x
  • Traefik: v3.0 (Docker provider + file provider)
  • Authelia: v4.37.5
  • Cloudflare Tunnel: Official cloudflare/cloudflared:latest
  • Protected services: Vaultwarden, Photoprism

2. docker-compose.yml

version: "3.8"
services:
  traefik:
    image: traefik:v3.0
    container_name: traefik
    restart: unless-stopped
    command:
      - --api.dashboard=true
      - --entrypoints.web.address=:80
      - --entrypoints.websecure.address=:443
      - --providers.docker=true
      - --providers.docker.exposedbydefault=false
      - --providers.file.filename=/etc/traefik/middlewares.yml
      - --providers.file.watch=true
      - --log.level=DEBUG
    environment:
      - CF_DNS_API_TOKEN=<CLOUDFLARE_API_TOKEN>
    volumes:
      - ./traefik/middlewares.yml:/etc/traefik/middlewares.yml:ro
      - ./traefik/letsencrypt:/letsencrypt
      - /var/run/docker.sock:/var/run/docker.sock:ro
    networks:
      - infra_net
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.traefik.rule=Host(`traefik.xxxxx.com`)"
      - "traefik.http.routers.traefik.entrypoints=websecure"
      - "traefik.http.routers.traefik.tls.certresolver=cloudflare"

  authelia:
    image: authelia/authelia:4.37.5
    container_name: authelia
    restart: unless-stopped
    expose:
      - "9091"
    volumes:
      - ./authelia/config:/config:ro
    networks:
      - infra_net
    depends_on:
      redis:
        condition: service_healthy
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.authelia.rule=Host(`authelia.xxxxx.com`)"
      - "traefik.http.routers.authelia.entrypoints=websecure"
      - "traefik.http.routers.authelia.tls.certresolver=cloudflare"
      - "traefik.http.services.authelia.loadbalancer.server.port=9091"

  redis:
    image: valkey/valkey:8-alpine
    container_name: authelia-redis
    healthcheck:
      test: ["CMD", "valkey-server", "--version"]
      interval: 10s
      timeout: 5s
      retries: 3
      start_period: 10s
    networks:
      - infra_net
    volumes:
      - ./authelia/redis:/data

  vaultwarden:
    image: vaultwarden/server:latest
    container_name: vaultwarden
    restart: unless-stopped
    expose:
      - "80"
    volumes:
      - ./vaultwarden/data:/data
    networks:
      - infra_net
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.vaultwarden.rule=Host(`vaultwarden.xxxxx.com`)"
      - "traefik.http.routers.vaultwarden.entrypoints=websecure"
      - "traefik.http.routers.vaultwarden.tls=true"
      - "traefik.http.routers.vaultwarden.tls.certresolver=cloudflare"
      - "traefik.http.services.vaultwarden.loadbalancer.server.port=80"
      - "traefik.http.routers.vaultwarden.middlewares=\
cloudflare-whitelist@file,\
inject-original-url@file,\
authelia@file,\
auth-redirect-vault@file"

  cloudflared:
    image: cloudflare/cloudflared:latest
    container_name: cloudflared
    restart: unless-stopped
    command: tunnel run --token <TUNNEL_TOKEN>
    networks:
      - infra_net

networks:
  infra_net:
    driver: bridge

3. traefik/middlewares.yml

http:
  middlewares:

    # 1. Whitelist Cloudflare IPs + Docker network
    cloudflare-whitelist:
      ipAllowList:
        sourceRange:
          - 103.21.244.0/22
          - 104.16.0.0/13
          # … other CF ranges …
          - 172.29.44.0/22   # Docker subnet of cloudflared

    # 2. Inject X-Original-URL for Authelia
    inject-original-url:
      headers:
        customRequestHeaders:
          X-Original-URL: "https://${req.host}${req.uri}"

    # 3. ForwardAuth to Authelia
    authelia:
      forwardAuth:
        address: http://authelia:9091/api/verify
        trustForwardHeader: true
        authResponseHeaders:
          - Remote-User
          - Remote-Groups
          - Remote-Name
          - Remote-Email

    # 4. Error redirect for 401 → Authelia UI
    auth-redirect-vault:
      errors:
        status:
          - "401"
        service: authelia@docker
        query: "/api/redirect?rd=https://vaultwarden.xxxxx.com"

4. authelia/config/configuration.yml

server:
  host: 0.0.0.0
  port: 9091

access_control:
  default_policy: one_factor
  rules:
    - domain: vaultwarden.xxxxx.com
      policy: one_factor
    - domain: authelia.xxxxx.com
      policy: bypass

session:
  name: authelia_session
  secret: "<SESSION_SECRET>"
  domain: xxxxx.com

storage:
  local:
    path: /config/db.sqlite3

notifier:
  filesystem:
    filename: /config/notification.txt

5. What’s Happening

  • Every request to https://vaultwarden.xxxxx.com returns 401 Unauthorized immediately.
  • I expect Traefik → ForwardAuth → Authelia UI (when not logged in) → after login I get back to Vaultwarden.

6. Logs & Diagnostics

  1. Traefik debug log shows lines like:
    Rejecting IP 172.29.44.5: no match in ipAllowList
    
  2. I’ve confirmed my Cloudflared container IP is inside 172.29.44.0/22 via:
    docker network inspect infra_net
    
  3. Order of middlewares on the Vaultwarden router:
    1. cloudflare-whitelist
    2. inject-original-url
    3. authelia (forwardAuth)
    4. auth-redirect-vault (errors)

7. What I’ve Tried

  • Adding all Cloudflare IPv4 & IPv6 ranges to ipAllowList
  • Including the Docker subnet (172.29.44.0/22)
  • Swapping middleware order
  • Verifying X-Original-URL header is injected correctly

8. Questions

  1. Am I missing a Traefik middleware ordering detail?
  2. Should I use inflightreq or another Traefik router option?
  3. Any gotchas with Cloudflare Tunnel IPs vs Docker overlay?

Thanks in advance for any guidance!

I don't think Traefik dynamic configuration supports runtime variables like ${req.host} or ${req.uri} in header values or elsewhere.

    # 2. Inject X-Original-URL for Authelia
    inject-original-url:
      headers:
        customRequestHeaders:
          X-Original-URL: "https://${req.host}${req.uri}"

Enable and check Traefik access log in JSON format (doc), what’s the output during requests?

Hello,
I enabled the access logs in JSON format as you suggested and confirmed that the X-Original-URL header does not appear in the logs. It looks like Traefik indeed doesn't interpret ${req.host} or ${req.uri} as dynamic runtime variables — the value is treated as a literal string.

I’ll try to change the way I’m handling this and look for an alternative method to pass the original URL to Authelia.
Thanks a lot for the insight!

Traefik automatically passes a lot of X-Forwarded-… headers already.

Follow-Up: Traefik, Authelia, Cloudflare Tunnel - DNS Resolving to A Records Instead of CNAME

@bluepuma77, thanks for the help on my previous post about 401 Unauthorized errors with Traefik v3.0, Authelia, and Cloudflare Tunnel (original post). I resolved the 401 issue by removing the invalid X-Original-URL header (Traefik doesn’t support ${req.host}${req.uri}), consolidating duplicate http blocks in middlewares.yml, and removing redundant authelia-auth@file labels. However, I’m now facing a new issue: accessing https://myvault.example.com bypasses Authelia’s authentication entirely, taking me directly to Vaultwarden without prompting for two-factor authentication. Additionally, there’s a related DNS issue where dig @1.1.1.1 returns A records instead of the expected CNAME for my Cloudflare Tunnel, which might be contributing.

Current Issue

  • Authelia Bypassed: When I access https://myvault.example.com, I expect Traefik to route the request through Authelia’s authelia-auth middleware, redirecting to https://auth.example.com for two-factor authentication. Instead, the request goes straight to Vaultwarden, ignoring Authelia. This also happens for photos.example.com and traefik.example.com, but auth.example.com works as expected (bypasses authentication per configuration).

  • DNS Issue (Possibly Related): My Cloudflare DNS settings have CNAME records for traefik.example.com, auth.example.com, myvault.example.com, and photos.example.com pointing to <TUNNEL-UUID>.cfargotunnel.com with Proxy Status: Proxied. However, dig @1.1.1.1 returns A records (104.21.6.197, 172.67.135.58) instead of CNAME, which may prevent Cloudflare Tunnel from routing traffic correctly to Traefik.

Environment & Versions

  • Docker & Docker Compose: Docker Engine 24.x, Compose v2.x

  • Traefik: v3.0 (Docker provider + file provider)

  • Authelia: Latest (v4.38.x)

  • Cloudflare Tunnel: cloudflare/cloudflared:latest

  • Protected Services: Vaultwarden, Photoprism

  • Environment: QNAP NAS with Docker, services on a bridge network (infra_net)

Configurations

Below are my sanitized configuration files, reflecting fixes from the previous post (removed duplicate http block, removed redundant labels, set Authelia to two_factor).

docker-compose.yml

services:
  traefik:
    image: traefik:v3.0
    container_name: traefik
    restart: unless-stopped
    command:
      - --api.dashboard=true
      - --api.insecure=false
      - --entrypoints.web.address=:80
      - --entrypoints.websecure.address=:443
      - --providers.docker=true
      - --providers.docker.exposedbydefault=false
      - --certificatesresolvers.cloudflare.acme.dnschallenge=true
      - --certificatesresolvers.cloudflare.acme.dnschallenge.provider=cloudflare
      - --certificatesresolvers.cloudflare.acme.email=user@example.com
      - --certificatesresolvers.cloudflare.acme.storage=/letsencrypt/acme.json
      - --providers.file.filename=/etc/traefik/middlewares.yml
      - --providers.file.directory=/etc/traefik/
      - --providers.file.watch=true
      - --log.level=DEBUG
      - --accesslog=true
      - --accesslog.filepath=/logs/access.log
      - --entrypoints.web.forwardedHeaders.insecure=true
      - --entrypoints.websecure.forwardedHeaders.insecure=true
    ports:
      - "8880:80"
      - "8443:443"
    env_file: .env
    environment:
      - CF_DNS_API_TOKEN=${CF_DNS_API_TOKEN}
    volumes:
      - ./traefik/middlewares.yml:/etc/traefik/middlewares.yml:ro
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./traefik/letsencrypt:/letsencrypt
      - ./logs:/logs
    networks:
      - infra_net
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.traefik.rule=Host(`traefik.example.com`)"
      - "traefik.http.routers.traefik.service=api@internal"
      - "traefik.http.routers.traefik.entrypoints=websecure"
      - "traefik.http.routers.traefik.tls.certresolver=cloudflare"
      - "traefik.http.routers.traefik.middlewares=authelia-auth"

  authelia:
    image: authelia/authelia:latest
    container_name: authelia
    command: authelia --config /config/configuration.yml
    restart: unless-stopped
    expose:
      - "9091"
    volumes:
      - ./authelia/config:/config
    networks:
      - infra_net
    depends_on:
      redis:
        condition: service_healthy
    env_file: .env
    environment:
      - AUTHELIA_STORAGE_ENCRYPTION_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
      - AUTHELIA_SESSION_SECRET=${AUTHELIA_SESSION_SECRET}
      - AUTHELIA_JWT_SECRET=${AUTHELIA_JWT_SECRET}
    healthcheck:
      test: ["CMD", "authelia", "healthcheck", "--config", "/config/configuration.yml"]
      interval: 30s
      timeout: 20s
      retries: 3
      start_period: 60s
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.authelia.rule=Host(`auth.example.com`)"
      - "traefik.http.routers.authelia.entrypoints=websecure"
      - "traefik.http.routers.authelia.tls.certresolver=cloudflare"
      - "traefik.http.routers.authelia.service=authelia"

  redis:
    image: redis:7-alpine
    container_name: authelia-redis
    command: redis-server --save 30 1 --loglevel warning
    restart: unless-stopped
    volumes:
      - ./authelia/redis:/data
    networks:
      - infra_net
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 60s
      retries: 3
      start_period: 120s

  vaultwarden:
    image: vaultwarden/server:latest
    container_name: vaultwarden
    restart: unless-stopped
    command:
      - /start.sh
      - --websocket-enabled
      - --websocket-address=0.0.0.0
      - --websocket-port=3012
    expose:
      - "80"
      - "3012"
    volumes:
      - /share/Container/vaultwarden/data:/data
    environment:
      - WEBSOCKET_ENABLED=true
      - WEBSOCKET_ADDRESS=0.0.0.0
      - DOMAIN=https://myvault.example.com
    networks:
      - infra_net
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.vaultwarden.rule=Host(`myvault.example.com`)"
      - "traefik.http.routers.vaultwarden.entrypoints=websecure"
      - "traefik.http.routers.vaultwarden.tls.certresolver=cloudflare"
      - "traefik.http.routers.vaultwarden.middlewares=authelia-auth"
      - "traefik.http.services.vaultwarden.loadbalancer.server.port=80"

  cloudflared:
    image: cloudflare/cloudflared:latest
    container_name: cloudflared
    restart: unless-stopped
    user: root
    env_file: .env
    environment:
      - GOMAXPROCS=2
      - TZ=Europe/Lisbon
    volumes:
      - /share/Container/cloudflared/config:/etc/cloudflared
      - /share/Container/cloudflared/logs:/var/log
    command: tunnel --config /etc/cloudflared/config.yml run
    networks:
      - infra_net
    cap_add:
      - NET_ADMIN
    depends_on:
      - traefik
    healthcheck:
      test: ["CMD", "cloudflared", "tunnel", "list"]
      interval: 30s
      timeout: 10s
      retries: 5
      start_period: 30s

  photoprism:
    image: photoprism/photoprism:latest
    container_name: photoprism
    restart: unless-stopped
    depends_on:
      - traefik
    networks:
      - infra_net
    env_file: .env
    environment:
      - PHOTOPRISM_ADMIN_PASSWORD=${PHOTOPRISM_ADMIN_PASSWORD}
      - PHOTOPRISM_HTTP_PORT=2342
      - PHOTOPRISM_DEFAULT_TLS=false
      - PHOTOPRISM_ORIGINALS_LIMIT=5000
      - PHOTOPRISM_PUBLIC=false
      - PHOTOPRISM_LOG_LEVEL=info
    volumes:
      - /share/Container/Photoprism/Photos:/photoprism/originals
      - /share/Container/Photoprism/Upload:/photoprism/import
      - /share/Container/Photoprism/Config:/photoprism/storage
    expose:
      - "2342"
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.photoprism.rule=Host(`photos.example.com`)"
      - "traefik.http.routers.photoprism.entrypoints=websecure"
      - "traefik.http.routers.photoprism.tls.certresolver=cloudflare"
      - "traefik.http.routers.photoprism.middlewares=authelia-auth"
      - "traefik.http.services.photoprism.loadbalancer.server.port=2342"

networks:
  infra_net:
    driver: bridge

traefik/middlewares.yml

http:
  middlewares:
    authelia-auth:
      forwardAuth:
        address: http://authelia:9091/api/verify?rd=https://auth.example.com
        trustForwardHeader: true
        authResponseHeaders:
          - Remote-User
          - Remote-Groups
          - Remote-Email
          - Remote-Name
          - Remote-Preferred-Username
    auth-redirect:
      redirectRegex:
        regex: "^https://([^/]+)(/.*)?$"
        replacement: "https://auth.example.com/?rd=https://${1}${2}"
        permanent: false
  services:
    authelia:
      loadBalancer:
        servers:
          - url: http://authelia:9091
    vaultwarden:
      loadBalancer:
        servers:
          - url: http://vaultwarden:80
    photoprism:
      loadBalancer:
        servers:
          - url: http://photoprism:2342

authelia/config/configuration.yml

server:
  address: tcp://0.0.0.0:9091
log:
  level: debug
  format: text
authentication_backend:
  file:
    path: /config/users_database.yml
access_control:
  default_policy: deny
  rules:
    - domain: "myvault.example.com"
      policy: two_factor
    - domain: "auth.example.com"
      policy: bypass
    - domain: "photos.example.com"
      policy: two_factor
    - domain: "traefik.example.com"
      policy: two_factor
session:
  name: authelia_session
  secret: "${AUTHELIA_SESSION_SECRET}"
  expiration: 7200
  inactivity: 3600
  cookies:
    - domain: example.com
      authelia_url: https://auth.example.com
      default_redirection_url: https://myvault.example.com
      same_site: lax
  redis:
    host: redis
    port: 6379
identity_validation:
  reset_password:
    jwt_secret: "${AUTHELIA_JWT_SECRET}"
storage:
  local:
    path: /config/db.sqlite3
  encryption_key: "${AUTHELIA_STORAGE_ENCRYPTION_KEY}"
notifier:
  smtp:
    address: submission://smtp.gmail.com:587
    username: "user@example.com"
    password: "xxxxxxxxxxxxxxxx"
    sender: "user@example.com"
    subject: "[Authelia] {title}"
    startup_check_address: "user@example.com"
    tls:
      skip_verify: false

authelia/config/users_database.yml

users:
  user:
    password: "$argon2id$v=19$m=65536,t=3,p=4$xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx$xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
    displayname: "User Name"
    email: user@example.com
    groups:
      - admins

cloudflared/config/config.yml

tunnel: <TUNNEL-UUID>
credentials-file: /etc/cloudflared/<TUNNEL-UUID>.json
logfile: /var/log/cloudflared.log
ingress:
  - hostname: traefik.example.com
    service: http://traefik:80
  - hostname: auth.example.com
    service: http://authelia:9091
  - hostname: myvault.example.com
    service: http://vaultwarden:80
  - hostname: photos.example.com
    service: http://photoprism:2342
  - service: http_status:404
protocol: quic
heartbeat-interval: 5s
retries: 10
no-chunked-encoding: true
loglevel: info

Current Issue Details

  • Authelia Bypassed:

    • Accessing https://myvault.example.com goes directly to Vaultwarden without redirecting to https://auth.example.com for two-factor authentication.

    • Expected behavior: Traefik applies the authelia-auth middleware, triggers Authelia’s login page, and redirects back to Vaultwarden after authentication.

    • Same issue occurs for photos.example.com and traefik.example.com. Only auth.example.com works correctly (bypasses authentication as configured).

  • DNS Issue (Possibly Related):

    • Cloudflare DNS has CNAME records pointing to <TUNNEL-UUID>.cfargotunnel.com with Proxy Status: Proxied.

    • dig @1.1.1.1 myvault.example.com returns:

      ;; ANSWER SECTION:
      myvault.example.com. 300 IN A 104.21.6.197
      myvault.example.com. 300 IN A 172.67.135.58
      
      

      Expected:

      ;; ANSWER SECTION:
      myvault.example.com. 300 IN CNAME <TUNNEL-UUID>.cfargotunnel.com
      
      
    • This may prevent Cloudflare Tunnel from routing traffic to Traefik, potentially causing the Authelia bypass.

Previous Issue Resolution

  • Fixed 401 Unauthorized by:

    • Removing invalid X-Original-URL header with ${req.host}${req.uri}.

    • Consolidating duplicate http blocks in middlewares.yml.

    • Removing redundant authelia-auth@file labels.

    • Using two_factor authentication in Authelia for better security.

Steps Tried

  • Verified Cloudflare DNS settings (CNAME to <TUNNEL-UUID>.cfargotunnel.com, cf-proxied: true).

  • Deleted conflicting A records in Cloudflare.

  • Recreated CNAME records to force DNS refresh.

  • Rebooted NAS and cleared DNS caches (systemd-resolve --flush-caches).

  • Tested with dig @1.1.1.1 to bypass local resolver.

  • Checked Traefik, Authelia, and Cloudflared logs.

  • Ensured Traefik middleware order: authelia-auth followed by auth-redirect.

  • Tested in incognito mode to avoid browser caching.

Logs

  • Authelia:

    time="2025-08-05T12:52:06Z" level=info msg="Startup complete"
    time="2025-08-05T12:52:06Z" level=info msg="Listening for non-TLS connections on '[::]:9091' path '/'"
    
    
    • No authentication requests logged for myvault.example.com.
  • Traefik:

    • Access logs show requests to myvault.example.com reaching Vaultwarden directly, bypassing authelia-auth middleware.

    • No errors related to middleware application.

  • Cloudflared: Tunnel is active, no connection errors.

Questions

  1. Why is Traefik ignoring the authelia-auth middleware for myvault.example.com, photos.example.com, and traefik.example.com?

  2. Could the DNS issue (A records instead of CNAME) be causing Traefik to route traffic incorrectly, bypassing Authelia?

  3. Is my authelia-auth middleware configuration correct for forward authentication with Cloudflare Tunnel?

  4. Any debugging tips for Traefik middleware application or Cloudflare Tunnel routing?

Thanks for any insights or suggestions! I’m hoping to ensure Authelia’s two-factor authentication is enforced and resolve the DNS issue.

{Edited: a few problem details}

Docker-CE v24 is 1.5 years old, we are currently at v28, Traefk v3.0 is 1 years old, currently at v3.5. So I recommend to upgrade, I am sure some bugs have been fixed in the meantime.

Your vaultwarden config seems okay:

    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.vaultwarden.rule=Host(`myvault.example.com`)"
      - "traefik.http.routers.vaultwarden.entrypoints=websecure"
      - "traefik.http.routers.vaultwarden.tls.certresolver=cloudflare"
      - "traefik.http.routers.vaultwarden.middlewares=authelia-auth"
      - "traefik.http.services.vaultwarden.loadbalancer.server.port=80"

You could use =authelia-auth@file, not sure if that changes anything.

authelia-auth is set as forwardAuth middleware, so I would assume Traefik does recognize it. Do you see any requests arriving at the authelia container, is it maybe just responding with 200, so requests are okay'ed?

Enable and check Traefik debug log (doc), are routers with middleware created? Enable and check Traefik access log in JSON format (doc), what’s the output during requests?