Previously every example.com had to be found and replaced manually.
Now a single DOMAIN=yourdomain.com in .env propagates everywhere:
- .env.example: add DOMAIN=example.com with explanation
- docker-compose.yml: pass DOMAIN into authelia container environment
- authelia/configuration.yml: use {{ env "DOMAIN" }} in totp.issuer,
access_control.rules, and all four session.cookies[] fields
(Authelia 4.38+ Go template substitution)
- caddy/Caddyfile: use {env.DOMAIN} in all site block addresses
(Caddy native env substitution); update header comment explaining
how to set DOMAIN for system vs dockerized Caddy
- README.md: update step 3 to explain DOMAIN is the only change needed;
update step 4 to say just uncomment the right rule; update Caddy
wiring section with DOMAIN env var instructions for both Caddy modes
https://claude.ai/code/session_012eTokAaGiZo7aGt1T2W9BC
211 lines
8.2 KiB
Caddyfile
211 lines
8.2 KiB
Caddyfile
# =============================================================================
|
|
# Caddyfile -- Authelia + fail2ban integration
|
|
#
|
|
# Copy this file into your Caddy setup (or merge the relevant blocks into
|
|
# your existing Caddyfile), then reload:
|
|
#
|
|
# # System Caddy:
|
|
# sudo caddy validate --config /etc/caddy/Caddyfile
|
|
# sudo systemctl reload caddy
|
|
#
|
|
# # Dockerized Caddy:
|
|
# docker compose exec caddy caddy reload --config /etc/caddy/Caddyfile
|
|
#
|
|
# Domain is read from the DOMAIN environment variable -- no hardcoded
|
|
# domains in this file. Set it in your Caddy environment before reloading:
|
|
#
|
|
# # System Caddy (add to /etc/caddy/caddy.env or your systemd override):
|
|
# export DOMAIN=yourdomain.com
|
|
#
|
|
# # Dockerized Caddy (add to its .env or compose environment:):
|
|
# DOMAIN=yourdomain.com
|
|
#
|
|
# Only the upstream IPs need editing: replace 192.168.x.x with real LAN IPs.
|
|
#
|
|
# Requirements:
|
|
# - Caddy v2.5.1+ (for `forward_auth` directive; tested on v2.11.2)
|
|
# - Caddy must be on the `caddy_net` Docker network so it can resolve
|
|
# `authelia` by container name. In your Caddy compose:
|
|
# networks: [caddy_net]
|
|
# and at the bottom:
|
|
# networks:
|
|
# caddy_net:
|
|
# external: true
|
|
#
|
|
# =============================================================================
|
|
# DECISION TREE -- which sites go behind Authelia?
|
|
#
|
|
# CASE 1 -- App has NO built-in auth (e.g. Pi doorbell PTT page).
|
|
# `import authelia` + rule in authelia/configuration.yml.
|
|
# Authelia is the ONLY login. Use two_factor for hardware-control pages.
|
|
#
|
|
# CASE 2 -- App has built-in auth AND supports trusted-header proxy auth
|
|
# (Frigate 0.14+, Grafana, Gitea, Nextcloud, Home Assistant, ...).
|
|
# `import authelia` + rule in Authelia + disable the app's own login form.
|
|
# Single Authelia login: Authelia authenticates, app reads Remote-User header.
|
|
#
|
|
# CASE 3 -- App has built-in auth and CANNOT switch to proxy auth, but you
|
|
# want a 2FA gate in front anyway (router admin, legacy apps, etc.).
|
|
# `import authelia` + rule in Authelia. App auth is untouched.
|
|
# User logs into Authelia (2FA), then the app's own login form appears.
|
|
#
|
|
# CASE 4 -- App handles its own auth; Authelia not involved.
|
|
# Plain `reverse_proxy`, no `import authelia`, no Authelia rule.
|
|
# Traffic skips Authelia entirely.
|
|
#
|
|
# fail2ban coverage: import (accesslog) in EVERY site block -- gated or not.
|
|
# The caddy-4xx jail watches /var/log/caddy/access.log and bans scanners
|
|
# spraying all your subdomains, not just the Authelia-gated ones.
|
|
# =============================================================================
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# (authelia) -- forward_auth gate.
|
|
# Import into any site block you want gated (cases 1, 2, 3).
|
|
# On success Authelia sets Remote-User, Remote-Groups, Remote-Email,
|
|
# Remote-Name headers that the upstream app can consume for role mapping.
|
|
# -----------------------------------------------------------------------------
|
|
(authelia) {
|
|
forward_auth authelia:9091 {
|
|
uri /api/authz/forward-auth
|
|
copy_headers Remote-User Remote-Groups Remote-Email Remote-Name
|
|
}
|
|
}
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# (accesslog) -- structured JSON access log consumed by fail2ban's caddy-4xx
|
|
# jail. Import into EVERY site block so fail2ban covers your whole stack.
|
|
#
|
|
# Pre-create the log directory before starting Caddy:
|
|
# sudo mkdir -p /var/log/caddy
|
|
# sudo chown caddy:caddy /var/log/caddy # system Caddy
|
|
# # Dockerized Caddy: add volumes: ["/var/log/caddy:/var/log/caddy"] to compose
|
|
# -----------------------------------------------------------------------------
|
|
(accesslog) {
|
|
log {
|
|
output file /var/log/caddy/access.log {
|
|
roll_size 10MiB
|
|
roll_keep 5
|
|
roll_keep_for 720h
|
|
}
|
|
format json
|
|
}
|
|
}
|
|
|
|
# =============================================================================
|
|
# Authelia login portal
|
|
# Never add `import authelia` here -- the `bypass` rule in
|
|
# access_control.rules handles the portal itself. Adding forward_auth here
|
|
# would cause a redirect loop.
|
|
# =============================================================================
|
|
auth.{env.DOMAIN} {
|
|
import accesslog
|
|
reverse_proxy authelia:9091
|
|
}
|
|
|
|
# =============================================================================
|
|
# CASE 2: Frigate UI -- Authelia replaces Frigate's own login form.
|
|
#
|
|
# Frigate 0.14+ supports trusted-header proxy auth. Authelia authenticates
|
|
# the user (optionally with TOTP 2FA), then passes Remote-User and
|
|
# Remote-Groups headers to Frigate which maps them to admin/viewer roles.
|
|
#
|
|
# To enable proxy auth in Frigate, edit frigate_config/config.yml:
|
|
#
|
|
# auth:
|
|
# enabled: False
|
|
# trusted_proxies:
|
|
# - 172.18.0.0/16 # caddy_net subnet; find it with:
|
|
# # docker network inspect caddy_net
|
|
# proxy:
|
|
# header_map:
|
|
# user: remote-user # matches copy_headers in (authelia) snippet
|
|
# role: remote-groups
|
|
# default_role: viewer
|
|
# separator: '|'
|
|
# # Optional shared secret -- prevents LAN header spoofing.
|
|
# # Generate: openssl rand -hex 32
|
|
# # Set the same value as header_up X-Proxy-Secret below.
|
|
# # auth_secret: 'your-32-byte-hex'
|
|
#
|
|
# Then uncomment the cam rule in authelia/configuration.yml and restart:
|
|
# docker compose restart authelia (in the authelia stack)
|
|
# docker compose restart frigate (in the camera stack)
|
|
# =============================================================================
|
|
cam.{env.DOMAIN} {
|
|
import accesslog
|
|
import authelia
|
|
|
|
reverse_proxy 192.168.x.x:8971 { # CHANGE IP
|
|
transport http {
|
|
read_timeout 60s
|
|
write_timeout 60s
|
|
}
|
|
# Uncomment if you set auth_secret: in Frigate's proxy: block.
|
|
# header_up X-Proxy-Secret "your-32-byte-hex-here"
|
|
}
|
|
}
|
|
|
|
# =============================================================================
|
|
# CASE 1: Pi doorbell PTT page -- Authelia is the ONLY auth.
|
|
#
|
|
# The Pi's Flask server has no built-in authentication. Authelia gates it.
|
|
# two_factor is appropriate -- this URL controls a speaker in your house.
|
|
# Comment out until the Pi is deployed.
|
|
# Also uncomment the doorbell rule in authelia/configuration.yml.
|
|
# =============================================================================
|
|
# doorbell.{env.DOMAIN} {
|
|
# import accesslog
|
|
# import authelia
|
|
#
|
|
# # Same-origin proxy to Frigate so WebRTC fetch works without CORS.
|
|
# handle_path /frigate/* {
|
|
# reverse_proxy 192.168.x.x:8971 { # CHANGE IP
|
|
# transport http {
|
|
# read_timeout 60s
|
|
# write_timeout 60s
|
|
# }
|
|
# }
|
|
# }
|
|
#
|
|
# handle {
|
|
# reverse_proxy 192.168.x.x:5555 # CHANGE IP
|
|
# }
|
|
# }
|
|
|
|
# =============================================================================
|
|
# CASE 3: App keeps its own login; Authelia adds a 2FA gate in front.
|
|
#
|
|
# Use when an app can't do proxy auth but you still want 2FA before it.
|
|
# The user authenticates with Authelia (2FA), then the app's own login
|
|
# form appears. Two separate logins -- the app's auth is untouched.
|
|
#
|
|
# Also add a rule in authelia/configuration.yml:
|
|
# - domain: 'nas.{{ env "DOMAIN" }}'
|
|
# policy: 'two_factor'
|
|
# =============================================================================
|
|
# nas.{env.DOMAIN} { # CHANGE/REMOVE example
|
|
# import accesslog
|
|
# import authelia
|
|
#
|
|
# reverse_proxy 192.168.x.x:PORT { # CHANGE
|
|
# transport http {
|
|
# tls_insecure_skip_verify # only if self-signed TLS
|
|
# }
|
|
# }
|
|
# }
|
|
|
|
# =============================================================================
|
|
# CASE 4: App handles its own auth; Authelia not involved.
|
|
#
|
|
# No `import authelia`. No access_control rule in Authelia.
|
|
# Still import accesslog so fail2ban's caddy-4xx jail covers this site.
|
|
# =============================================================================
|
|
# router.{env.DOMAIN} { # CHANGE/REMOVE example
|
|
# import accesslog
|
|
# reverse_proxy 192.168.x.x:PORT { # CHANGE
|
|
# transport http {
|
|
# tls_insecure_skip_verify
|
|
# }
|
|
# }
|
|
# }
|