--- ############################################################################### # Authelia configuration # # - File-based user database (no LDAP) # - SQLite local storage (no Redis, no MySQL/Postgres) # - Filesystem notifier (writes "emails" to /config/notifications/...) # - Argon2id password hashing # - Per-domain access policies under access_control.rules # # Secrets are NOT in this file. They are loaded from files mounted at # /secrets via the AUTHELIA_*_FILE env vars in docker-compose.yml. # # Your domain comes from the DOMAIN variable in .env -- no manual # find-and-replace needed. Authelia 4.38+ processes this file as a Go # template, so {{ env "DOMAIN" }} is substituted at startup. # # After editing, validate before restarting: # docker compose run --rm authelia authelia validate-config --config /config/configuration.yml ############################################################################### theme: 'dark' server: address: 'tcp://0.0.0.0:9091' buffers: read: 8192 write: 8192 log: level: 'info' format: 'text' # fail2ban filter expects text format -- don't change to json file_path: '/config/authelia.log' keep_stdout: true # also log to stdout for `docker logs` identity_validation: reset_password: jwt_lifespan: '5 minutes' jwt_algorithm: 'HS256' # jwt_secret loaded via AUTHELIA_IDENTITY_VALIDATION_RESET_PASSWORD_JWT_SECRET_FILE totp: disable: false issuer: '{{ env "DOMAIN" }}' # shown in your authenticator app algorithm: 'sha1' digits: 6 period: 30 authentication_backend: password_change: disable: false password_reset: disable: false refresh_interval: '5 minutes' file: path: '/config/users_database.yml' password: algorithm: 'argon2' argon2: variant: 'argon2id' iterations: 3 memory: 65536 parallelism: 4 key_length: 32 salt_length: 16 # --------------------------------------------------------------------------- # Access control # # default_policy: deny -- every domain Caddy forward_auths here must have # an explicit rule. Domains with no `import authelia` in Caddy never reach # Authelia at all, so the deny doesn't apply to them. # # Policies: # bypass Authelia waves the request through (used for the portal) # one_factor password only # two_factor password + TOTP # # --------------------------------------------------------------------------- # WHICH SITES NEED A RULE HERE? # # CASE 1 -- App has NO built-in auth (e.g. Pi doorbell PTT page). # -> Rule required + `import authelia` in Caddy. # -> 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 ...). # -> Rule required + `import authelia` in Caddy + disable the app's login # form in its own config (see README.md per-app instructions). # -> Single Authelia login covers both access AND the app's user/role # mapping via Remote-User / Remote-Groups headers. # # CASE 3 -- App has built-in auth and CANNOT switch to proxy auth, but you # still want a 2FA gate in front of it (extra security layer). # -> Rule required + `import authelia` in Caddy. # -> User logs into Authelia (2FA) THEN into the app's own login form. # Two separate logins -- the app auth is unchanged. # -> Useful for: router admin pages, NAS UIs, any legacy app. # # CASE 4 -- App handles its own auth and you don't want Authelia involved. # -> NO rule here + NO `import authelia` in Caddy. # -> Traffic skips Authelia entirely; the app handles everything. # --------------------------------------------------------------------------- access_control: default_policy: 'deny' rules: # The Authelia portal itself is always bypass. - domain: 'auth.{{ env "DOMAIN" }}' policy: 'bypass' # =================================================================== # CASE 1 -- No app auth. Authelia is the only gate. # Example: Pi doorbell PTT page (Flask server, no built-in auth). # two_factor is right -- this URL controls a speaker in your house. # # ALSO PASTE INTO CADDYFILE (case 1 site block): # doorbell.{env.DOMAIN} { # import accesslog # import authelia # handle_path /frigate/* { # reverse_proxy 192.168.x.x:8971 # CHANGE: Frigate IP # } # handle { # reverse_proxy 192.168.x.x:5555 # CHANGE: Pi IP # } # } # =================================================================== # - domain: 'doorbell.{{ env "DOMAIN" }}' # policy: 'two_factor' # =================================================================== # CASE 2a -- App supports trusted-header proxy auth. # Authelia replaces the app's login form. Single login; the app reads # Remote-User from the upstream request for its own role mapping. # Example: Frigate 0.14+. # # ALSO PASTE INTO CADDYFILE (case 2a site block): # cam.{env.DOMAIN} { # import accesslog # import authelia # reverse_proxy 192.168.x.x:8971 { # CHANGE: Frigate IP # transport http { read_timeout 60s; write_timeout 60s } # # header_up X-Proxy-Secret "32-byte-hex" # if Frigate auth_secret set # } # } # # ALSO EDIT frigate_config/config.yml IN THE FRIGATE REPO: # auth: # enabled: False # trusted_proxies: [172.18.0.0/16] # caddy_net subnet # proxy: # header_map: {user: remote-user, role: remote-groups} # default_role: viewer # separator: '|' # =================================================================== # - domain: 'cam.{{ env "DOMAIN" }}' # policy: 'two_factor' # =================================================================== # CASE 2b -- App supports OIDC. Authelia is the OIDC provider. # Caddy block is identical to case 2a; the difference is on the app # side (token exchange, not header). REQUIRES additional setup of # identity_providers.oidc below this access_control block, with one # client per app -- see Authelia OIDC docs. # Example: Audiobookshelf. # # ALSO PASTE INTO CADDYFILE (case 2b site block): # books.{env.DOMAIN} { # import accesslog # import authelia # reverse_proxy 192.168.x.x:13378 # CHANGE IP # } # =================================================================== # - domain: 'books.{{ env "DOMAIN" }}' # policy: 'two_factor' # =================================================================== # CASE 3 -- App keeps its own login. Authelia adds a 2FA gate in # front. User authenticates with Authelia (2FA), then with the app # itself. Two logins, but Authelia's 2FA covers apps that don't # support proxy headers OR OIDC OR native TOTP. # Example: Uptime Kuma, Portainer (without OIDC), router admin. # # ALSO PASTE INTO CADDYFILE (case 3 site block): # uptime.{env.DOMAIN} { # import accesslog # import authelia # reverse_proxy 192.168.x.x:3001 # CHANGE IP # } # =================================================================== # - domain: 'uptime.{{ env "DOMAIN" }}' # policy: 'two_factor' # =================================================================== # CASE 4 -- App handles its own auth. NO rule needed here. # Authelia is never consulted. Caddy still imports accesslog so # fail2ban watches the subdomain for scanners. # Example: Plex/Emby (native clients break with Authelia redirects), # Syncthing, anything you've decided to leave alone. # # ONLY PASTE INTO CADDYFILE -- nothing here in configuration.yml: # plex.{env.DOMAIN} { # import accesslog # NO import authelia # reverse_proxy 192.168.x.x:32400 # CHANGE IP # } # =================================================================== # (no rule -- case 4 is the absence of one) session: # secret loaded via AUTHELIA_SESSION_SECRET_FILE cookies: - name: 'authelia_session' domain: '{{ env "DOMAIN" }}' authelia_url: 'https://auth.{{ env "DOMAIN" }}' default_redirection_url: 'https://{{ env "DOMAIN" }}' expiration: '1 hour' inactivity: '5 minutes' remember_me: '1 month' same_site: 'lax' # In-app rate limiting. Locks the user account after repeated failures. # fail2ban is the second line of defense: it bans the source IP. # Together: Authelia locks the *user*, fail2ban bans the *IP*. regulation: max_retries: 3 find_time: '2 minutes' ban_time: '5 minutes' storage: # encryption_key loaded via AUTHELIA_STORAGE_ENCRYPTION_KEY_FILE local: path: '/config/db.sqlite3' # Filesystem notifier -- password reset / new device emails get written to # a file you can `tail -f`. Swap to `smtp:` when you wire up a real # transactional sender (see README.md "Switching the notifier to SMTP"). notifier: disable_startup_check: false filesystem: filename: '/config/notifications/notification.txt'