Skip to content

Instantly share code, notes, and snippets.

@SeeThruHead
Created December 21, 2025 08:40
Show Gist options
  • Select an option

  • Save SeeThruHead/a9c3b7e5f3236e44decfccb699790586 to your computer and use it in GitHub Desktop.

Select an option

Save SeeThruHead/a9c3b7e5f3236e44decfccb699790586 to your computer and use it in GitHub Desktop.

Exposing Homelab Services via Cloudflare Tunnel with Authelia SSO

This guide covers setting up external access to your homelab services through Cloudflare Tunnels, with Authelia providing single sign-on protection. The end result: you can access sonarr.yourdomain.com from anywhere, protected by 2FA, without opening any ports on your router.

The Traffic Flow

Internet → Cloudflare → cloudflared container → Traefik → Authelia check → Your service
  1. User visits sonarr.yourdomain.com
  2. Cloudflare routes it through your tunnel to the cloudflared container
  3. cloudflared forwards to Traefik
  4. Traefik checks with Authelia: "Is this user logged in?"
  5. If not, redirect to auth.yourdomain.com to authenticate
  6. Once authenticated, Traefik forwards to Sonarr

Prerequisites

  • A domain managed by Cloudflare (free tier works)
  • A Linux server running Docker
  • Basic Docker Compose knowledge

Directory Structure

mkdir -p ~/docker-apps/authelia/{config,certs,dynamic}
mkdir -p ~/docker-apps/cloudflared

Step 1: Get Cloudflare Origin Certificates

These certs let Traefik terminate TLS for traffic coming through the tunnel.

  1. Go to Cloudflare Dashboard
  2. Select your domain
  3. SSL/TLS, then Origin Server
  4. Create Certificate
  5. Set hostnames to *.yourdomain.com and yourdomain.com
  6. Key type: RSA 2048
  7. Validity: 15 years
  8. Save both the certificate and private key

Put them on your server:

vi ~/docker-apps/authelia/certs/origin-cert.pem
# Paste the Origin Certificate

vi ~/docker-apps/authelia/certs/origin-key.pem
# Paste the Private Key

Step 2: Create a Cloudflare Tunnel

  1. Cloudflare Dashboard, then Zero Trust
  2. Networks, then Tunnels
  3. Create a tunnel, give it a name
  4. Copy the tunnel token (you'll need this)

For the public hostname config in the Cloudflare dashboard, set up a wildcard:

  • Subdomain: *
  • Domain: yourdomain.com
  • Service: https://traefik:443
  • Under Additional application settings, TLS, set Origin Server Name to yourdomain.com
  • Enable No TLS Verify (since we're using origin certs, not public CAs)

Or you can manage routes via config file instead of the dashboard. More on that below.

Step 3: Traefik TLS Configuration

Create the dynamic TLS config:

vi ~/docker-apps/authelia/dynamic/tls.yml
tls:
  certificates:
    - certFile: /etc/traefik/certs/origin-cert.pem
      keyFile: /etc/traefik/certs/origin-key.pem

Step 4: Authelia Configuration

Create the main config file:

vi ~/docker-apps/authelia/config/configuration.yml
server:
  address: tcp://0.0.0.0:9091

log:
  level: info

theme: dark

authentication_backend:
  file:
    path: /config/users_database.yml
    password:
      algorithm: argon2id
      iterations: 3
      memory: 65536
      parallelism: 4

totp:
  disable: false
  issuer: auth.yourdomain.com
  period: 30
  skew: 1

webauthn:
  disable: false
  display_name: Authelia

session:
  name: authelia_session
  domain: yourdomain.com
  expiration: 1h
  inactivity: 5m
  remember_me: 1M

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

access_control:
  default_policy: deny
  rules:
    # Public paths for apps like Wizarr that need unauthenticated access
    # for invitation links
    - domain: wizarr.yourdomain.com
      resources:
        - '^/join(/.*)?$'
        - '^/j(/.*)?$'
        - '^/static(/.*)?$'
      policy: bypass
    
    # Everything else requires 2FA
    - domain: "*.yourdomain.com"
      policy: two_factor

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

Create the users database:

vi ~/docker-apps/authelia/config/users_database.yml
users:
  yourusername:
    displayname: "Your Name"
    password: "$argon2id$v=19$m=65536,t=3,p=4$YOUR_HASH_HERE"
    email: you@email.com
    groups:
      - admins
      - users

Generate a password hash:

docker run --rm authelia/authelia:latest authelia crypto hash generate argon2 --password 'yourpassword'

Copy the output hash into the users file.

Step 5: Docker Compose

This is the main stack with Traefik, Authelia, and cloudflared all together:

version: "3.9"

services:
  traefik:
    image: traefik:v3.2
    container_name: traefik
    restart: unless-stopped
    ports:
      - "443:443"
    networks:
      - tunnel_network
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ~/docker-apps/authelia/certs:/etc/traefik/certs:ro
      - ~/docker-apps/authelia/dynamic:/etc/traefik/dynamic:ro
    command:
      - "--api.dashboard=true"
      - "--providers.docker=true"
      - "--providers.docker.exposedbydefault=false"
      - "--providers.file.directory=/etc/traefik/dynamic"
      - "--providers.file.watch=true"
      - "--entrypoints.websecure.address=:443"
      - "--log.level=INFO"
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.traefik.rule=Host(`traefik.yourdomain.com`)"
      - "traefik.http.routers.traefik.entrypoints=websecure"
      - "traefik.http.routers.traefik.tls=true"
      - "traefik.http.routers.traefik.service=api@internal"
      - "traefik.http.routers.traefik.middlewares=authelia@docker"

  authelia:
    image: authelia/authelia:latest
    container_name: authelia
    restart: unless-stopped
    networks:
      - tunnel_network
    volumes:
      - ~/docker-apps/authelia/config:/config
    environment:
      - TZ=America/Toronto
    labels:
      - "traefik.enable=true"
      # Router for the login page
      - "traefik.http.routers.authelia.rule=Host(`auth.yourdomain.com`)"
      - "traefik.http.routers.authelia.entrypoints=websecure"
      - "traefik.http.routers.authelia.tls=true"
      - "traefik.http.routers.authelia.service=authelia"
      # Service definition
      - "traefik.http.services.authelia.loadbalancer.server.port=9091"
      # ForwardAuth middleware that other services will use
      - "traefik.http.middlewares.authelia.forwardAuth.address=http://authelia:9091/api/verify?rd=https://auth.yourdomain.com"
      - "traefik.http.middlewares.authelia.forwardAuth.trustForwardHeader=true"
      - "traefik.http.middlewares.authelia.forwardAuth.authResponseHeaders=Remote-User,Remote-Groups,Remote-Name,Remote-Email"

  cloudflared:
    image: cloudflare/cloudflared:latest
    container_name: cloudflared
    restart: unless-stopped
    command: tunnel run
    environment:
      - TUNNEL_TOKEN=your_tunnel_token_here
    networks:
      - tunnel_network

networks:
  tunnel_network:
    name: tunnel_network

If you prefer config file based tunnel management instead of the Cloudflare dashboard, mount a config directory and use a config file:

cloudflared:
  image: cloudflare/cloudflared:latest
  container_name: cloudflared
  restart: unless-stopped
  command: tunnel run
  volumes:
    - ~/docker-apps/cloudflared:/home/nonroot/.cloudflared
  networks:
    - tunnel_network

With a config file at ~/docker-apps/cloudflared/config.yml:

tunnel: your-tunnel-id
credentials-file: /home/nonroot/.cloudflared/your-tunnel-id.json
ingress:
  - service: https://traefik:443
    originRequest:
      noTLSVerify: true

Step 6: Adding a Protected Service

Here's how to add Sonarr behind Authelia:

sonarr:
  image: linuxserver/sonarr:latest
  container_name: sonarr
  restart: unless-stopped
  networks:
    - tunnel_network
  volumes:
    - ~/docker-apps/sonarr:/config
  environment:
    - TZ=America/Toronto
  labels:
    - "traefik.enable=true"
    - "traefik.http.routers.sonarr.rule=Host(`sonarr.yourdomain.com`)"
    - "traefik.http.routers.sonarr.entrypoints=websecure"
    - "traefik.http.routers.sonarr.tls=true"
    - "traefik.http.routers.sonarr.middlewares=authelia@docker"
    - "traefik.http.services.sonarr.loadbalancer.server.port=8989"

The key line is middlewares=authelia@docker. That tells Traefik to check with Authelia before allowing access.

Step 7: Services That Need Public Paths

Some services like Wizarr need certain paths to be accessible without authentication (for invitation links). You handle this in Authelia's access control config, not in Traefik labels:

# In configuration.yml access_control section
- domain: wizarr.yourdomain.com
  resources:
    - '^/join(/.*)?$'
    - '^/j(/.*)?$'
    - '^/static(/.*)?$'
    - '^/setup(/.*)?$'
    - '^/wizard(/.*)?$'
  policy: bypass

- domain: wizarr.yourdomain.com
  policy: two_factor

Order matters. Put the bypass rules before the catch-all rules.

Step 8: First Run

cd ~/docker-apps/authelia
docker compose up -d

Visit https://auth.yourdomain.com and you should see the Authelia login page. Log in with the credentials you set up in the users file. You'll be prompted to set up 2FA on first login.

Troubleshooting

Authelia shows "authentication_level: 0" and login doesn't work:

Check that the password hash in users_database.yml doesn't have quotes around it, or if it does, make sure they're consistent. Restart the authelia container after any config changes.

Certificate errors:

Make sure noTLSVerify: true is set in your cloudflared config since you're using Cloudflare origin certs, not publicly trusted ones.

Requests not reaching Authelia:

Check that all containers are on the same Docker network. You can verify connectivity:

docker exec -it traefik wget -O- http://authelia:9091/

ForwardAuth middleware not working:

The middleware name in your service labels must match exactly what's defined in Authelia's labels. If Authelia defines authelia.forwardAuth, then services use authelia@docker.

Adding More Services

The pattern is always the same:

  1. Put the container on the same Docker network
  2. Add Traefik labels with the host rule
  3. Include the authelia middleware
  4. If certain paths need to be public, add bypass rules in Authelia's access control

That's the whole setup. Once it's running, you get SSO across all your services with a single login, accessible from anywhere through Cloudflare's network.

@SeeThruHead
Copy link
Author

recommend you use a docker socket proxy with read permissions only for containes and events for traefik

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment