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.
Internet → Cloudflare → cloudflared container → Traefik → Authelia check → Your service
- User visits
sonarr.yourdomain.com - Cloudflare routes it through your tunnel to the
cloudflaredcontainer cloudflaredforwards to Traefik- Traefik checks with Authelia: "Is this user logged in?"
- If not, redirect to
auth.yourdomain.comto authenticate - Once authenticated, Traefik forwards to Sonarr
- A domain managed by Cloudflare (free tier works)
- A Linux server running Docker
- Basic Docker Compose knowledge
mkdir -p ~/docker-apps/authelia/{config,certs,dynamic}
mkdir -p ~/docker-apps/cloudflaredThese certs let Traefik terminate TLS for traffic coming through the tunnel.
- Go to Cloudflare Dashboard
- Select your domain
- SSL/TLS, then Origin Server
- Create Certificate
- Set hostnames to
*.yourdomain.comandyourdomain.com - Key type: RSA 2048
- Validity: 15 years
- 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- Cloudflare Dashboard, then Zero Trust
- Networks, then Tunnels
- Create a tunnel, give it a name
- 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.
Create the dynamic TLS config:
vi ~/docker-apps/authelia/dynamic/tls.ymltls:
certificates:
- certFile: /etc/traefik/certs/origin-cert.pem
keyFile: /etc/traefik/certs/origin-key.pemCreate the main config file:
vi ~/docker-apps/authelia/config/configuration.ymlserver:
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.txtCreate the users database:
vi ~/docker-apps/authelia/config/users_database.ymlusers:
yourusername:
displayname: "Your Name"
password: "$argon2id$v=19$m=65536,t=3,p=4$YOUR_HASH_HERE"
email: you@email.com
groups:
- admins
- usersGenerate a password hash:
docker run --rm authelia/authelia:latest authelia crypto hash generate argon2 --password 'yourpassword'Copy the output hash into the users file.
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_networkIf 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_networkWith 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: trueHere'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.
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_factorOrder matters. Put the bypass rules before the catch-all rules.
cd ~/docker-apps/authelia
docker compose up -dVisit 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.
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.
The pattern is always the same:
- Put the container on the same Docker network
- Add Traefik labels with the host rule
- Include the authelia middleware
- 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.
recommend you use a docker socket proxy with read permissions only for containes and events for traefik