Build a homelab auth gateway with Traefik and Authentik
A practical, secret-free walkthrough of using Traefik and Authentik as the front door for self-hosted services.
Running apps in a homelab is easy. Running them safely on real domains is where the design starts to matter.
The pattern I use is simple:
- Traefik owns ports
80and443. - Every app joins one shared Docker network called
proxy. - Apps do not publish host ports unless they truly need to.
- Authentik becomes the central login layer for apps that do not have their own authentication.
- Secrets stay in
.envfiles and never in public documentation.
This post documents the gateway pattern in a way you can copy for your own lab.
This article intentionally uses placeholders like
<your-domain>and<secret>. Never publish real API tokens, database passwords, S3 secrets, or private keys.
Architecture
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Internet
│
▼
Cloudflare DNS
│
▼
Traefik container :80/:443
│
├── public app route
├── HTTPS certificates via DNS challenge
└── Authentik forward-auth middleware
│
▼
Authentik embedded proxy outpost
│
▼
Protected Docker service
The important part is that Traefik is the only public edge. Services sit behind it on Docker networking.
The shared Docker network
Create one external network once:
1
docker network create proxy
Every public service joins it:
1
2
3
networks:
proxy:
external: true
Traefik baseline
A minimal Traefik service looks like this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
services:
traefik:
image: traefik:latest
container_name: traefik
restart: unless-stopped
security_opt:
- no-new-privileges:true
networks:
- proxy
ports:
- "80:80"
- "443:443"
env_file:
- .env
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./data/traefik.yml:/traefik.yml:ro
- ./data/acme.json:/acme.json
labels:
- "traefik.enable=true"
- "traefik.http.routers.traefik-secure.entrypoints=https"
- "traefik.http.routers.traefik-secure.rule=Host(`traefik.<your-domain>`)"
- "traefik.http.routers.traefik-secure.tls=true"
- "traefik.http.routers.traefik-secure.tls.certresolver=cloudflare"
- "traefik.http.routers.traefik-secure.service=api@internal"
networks:
proxy:
external: true
Store Cloudflare credentials in .env, not in the compose file:
CF_API_EMAIL=<your-cloudflare-email>
CF_DNS_API_TOKEN=<your-cloudflare-dns-token>
Authentik stack
Authentik needs a database and the server/worker pair:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
services:
postgresql:
image: postgres:16-alpine
restart: unless-stopped
env_file: .env
environment:
POSTGRES_DB: ${PG_DB:-authentik}
POSTGRES_USER: ${PG_USER:-authentik}
POSTGRES_PASSWORD: ${PG_PASS}
volumes:
- database:/var/lib/postgresql/data
server:
image: ghcr.io/goauthentik/server:2026.2.2
container_name: authentik-server
restart: unless-stopped
command: server
env_file: .env
networks:
- proxy
- authentik_internal
labels:
- "traefik.enable=true"
- "traefik.docker.network=proxy"
- "traefik.http.routers.authentik-secure.entrypoints=https"
- "traefik.http.routers.authentik-secure.rule=Host(`auth.<your-domain>`)"
- "traefik.http.routers.authentik-secure.tls=true"
- "traefik.http.routers.authentik-secure.tls.certresolver=cloudflare"
- "traefik.http.services.authentik.loadbalancer.server.port=9000"
# Reusable middleware for protected apps
- "traefik.http.middlewares.authentik.forwardauth.address=http://authentik-server:9000/outpost.goauthentik.io/auth/traefik"
- "traefik.http.middlewares.authentik.forwardauth.trustForwardHeader=true"
- "traefik.http.middlewares.authentik.forwardauth.authResponseHeaders=X-authentik-username,X-authentik-groups,X-authentik-email,X-authentik-name,X-authentik-uid"
worker:
image: ghcr.io/goauthentik/server:2026.2.2
restart: unless-stopped
command: worker
env_file: .env
volumes:
- /var/run/docker.sock:/var/run/docker.sock
volumes:
database:
networks:
proxy:
external: true
authentik_internal:
internal: true
Example secret file:
PG_DB=authentik
PG_USER=authentik
PG_PASS=<generate-a-long-password>
AUTHENTIK_SECRET_KEY=<generate-a-long-random-secret>
AUTHENTIK_ERROR_REPORTING__ENABLED=false
AUTHENTIK_HOST=https://auth.<your-domain>
Generate safe values:
1
openssl rand -base64 48
Protecting a service with Authentik
For a protected app, the HTTPS router gets this middleware:
1
- "traefik.http.routers.myapp-secure.middlewares=authentik@docker"
But that is only half of the setup. Authentik also needs a callback path for the same host:
1
2
3
4
5
6
- "traefik.http.routers.authentik-myapp-outpost.entrypoints=https"
- "traefik.http.routers.authentik-myapp-outpost.rule=Host(`myapp.<your-domain>`) && PathPrefix(`/outpost.goauthentik.io/`)"
- "traefik.http.routers.authentik-myapp-outpost.priority=100"
- "traefik.http.routers.authentik-myapp-outpost.tls=true"
- "traefik.http.routers.authentik-myapp-outpost.tls.certresolver=cloudflare"
- "traefik.http.routers.authentik-myapp-outpost.service=authentik"
Then create a Proxy Provider and Application inside Authentik:
- Provider type: Proxy Provider
- Mode: Forward auth / single application
- External host:
https://myapp.<your-domain> - Outpost: embedded proxy outpost
Testing
Check the app route:
1
curl -k -I https://myapp.<your-domain>/
Expected unauthenticated result:
1
2
HTTP/2 302
location: https://auth.<your-domain>/application/o/authorize/...
Check the outpost callback route:
1
curl -k -I https://myapp.<your-domain>/outpost.goauthentik.io/ping
Expected:
1
HTTP/2 204
If /ping is not 204, your callback router is missing or lower priority than the app router.
What not to protect with forward-auth
Do not put browser SSO in front of machine APIs that expect signed requests, webhooks, or CLI clients. Examples:
- S3 API endpoints
- container registry APIs
- webhook URLs
- OAuth callback endpoints not designed for it
For those, protect the admin UI but leave the API authentication native.
Backup checklist
1
2
cp docker-compose.yml backups/docker-compose.yml.$(date +%Y%m%d-%H%M%S)
cp .env backups/.env.$(date +%Y%m%d-%H%M%S)
Do not commit the backup folder if it contains .env files.
Final pattern
This gateway gives you one clean security story:
- Traefik handles routing and TLS.
- Authentik handles human login.
- Apps stay on private Docker networks.
Secrets stay out of public docs.
Related documentation
These posts connect to this topic and help build the bigger homelab picture:
- Self-host Headscale with a protected web UI — is a concrete example of protecting a dashboard with Authentik.
- Private Pi-hole DNS over Headscale with DNSCrypt and Authentik — extends the same Authentik and Traefik pattern to Pi-hole while keeping DNS private.
- Self-host OpenClaw with Docker, Traefik, Authentik, and Telegram — uses this gateway model for an AI operator service.
- Run Garage S3 object storage in a homelab — shows why human dashboards and machine APIs need different auth treatment.