# ============================================================================= # Authelia + fail2ban -- Caddy snippets # # These are SNIPPETS TO ADD to your existing Caddyfile, not a replacement # for it. Copy the (authelia) and (accesslog) snippet definitions once at # the top of your Caddyfile, then copy whichever site blocks apply. # # DOMAIN is read from the environment -- set it wherever your Caddy reads # env vars (Caddy's own .env, systemd EnvironmentFile, or compose env:). # Only the upstream IPs need manual editing. # # Caddy v2.5.1+ required; tested on v2.11.2. # ============================================================================= # ----------------------------------------------------------------------------- # Paste these two snippet definitions once, near the top of your Caddyfile. # ----------------------------------------------------------------------------- # Gate any site block with Authelia by adding `import authelia` inside it. (authelia) { forward_auth authelia:9091 { uri /api/authz/forward-auth copy_headers Remote-User Remote-Groups Remote-Email Remote-Name } } # JSON access log that fail2ban's caddy-4xx jail reads. # Add `import accesslog` to EVERY site block (gated or not) so fail2ban # catches scanners hitting all your subdomains, not just the protected ones. (accesslog) { log { output file /var/log/caddy/access.log { roll_size 10MiB roll_keep 5 roll_keep_for 720h } format json } } # ============================================================================= # Authelia portal -- always required; never put `import authelia` here. # ============================================================================= auth.{env.DOMAIN} { import accesslog reverse_proxy authelia:9091 } # ============================================================================= # CASE 1 -- App has NO built-in auth. Authelia is the only gate. # # Examples: Homer, Heimdall, Dozzle, Prometheus, Alertmanager, Gatus, # WatchYourLAN, NUT web UI. Use two_factor in # authelia/configuration.yml for any of these. # ============================================================================= # Homer / Heimdall dashboard (no auth whatsoever) homer.{env.DOMAIN} { import accesslog import authelia reverse_proxy 192.168.x.x:8080 # CHANGE IP:PORT } # Dozzle (Docker log viewer -- no auth by default) dozzle.{env.DOMAIN} { import accesslog import authelia reverse_proxy 192.168.x.x:8888 # CHANGE IP:PORT } # --- Prometheus --- # No authentication built in. Always gate it -- metrics expose internal details. # prom.{env.DOMAIN} { # import accesslog # import authelia # reverse_proxy 192.168.x.x:9090 # CHANGE IP # } # --- Alertmanager --- # No authentication built in. # alerts.{env.DOMAIN} { # import accesslog # import authelia # reverse_proxy 192.168.x.x:9093 # CHANGE IP # } # --- Gatus --- # Status / uptime page. Has optional built-in OIDC but simpler to gate here. # status.{env.DOMAIN} { # import accesslog # import authelia # reverse_proxy 192.168.x.x:8080 # CHANGE IP # } # --- WatchYourLAN --- # Network ARP scanner. No built-in auth. # lan.{env.DOMAIN} { # import accesslog # import authelia # reverse_proxy 192.168.x.x:8840 # CHANGE IP # } # --- NUT (Network UPS Tools) web UI --- # The NUT daemon (upsd) has no web UI itself. Common frontends -- NUT-Monitor, # upsd-web, various Docker images -- have minimal or no auth. Gate whichever # you run here. Port varies by image. # ups.{env.DOMAIN} { # import accesslog # import authelia # reverse_proxy 192.168.x.x:PORT # CHANGE IP:PORT # } # --- Pi doorbell PTT page (Flask server, no auth) --- # Uncomment when the Pi is deployed. # 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 # } # } # ============================================================================= # CASE 2a -- App supports trusted-header proxy auth. Authelia replaces its # own login form. Single login, app reads Remote-User for roles. # # Requires per-app config changes -- see notes in each block. # ============================================================================= # --- Frigate 0.14+ --- # In frigate_config/config.yml: # 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: '|' cam.{env.DOMAIN} { import accesslog import authelia reverse_proxy 192.168.x.x:8971 { # CHANGE IP transport http { read_timeout 60s; write_timeout 60s } # header_up X-Proxy-Secret "32-byte-hex" # if auth_secret: set in Frigate } } # --- Grafana --- # In grafana.ini (or GF_* env vars): # [auth.proxy] # enabled = true # header_name = Remote-User # header_property = username # auto_sign_up = true grafana.{env.DOMAIN} { import accesslog import authelia reverse_proxy 192.168.x.x:3000 # CHANGE IP } # --- Gitea / Forgejo --- # In app.ini: # [service] # ENABLE_REVERSE_PROXY_AUTHENTICATION = true # REVERSE_PROXY_TRUSTED_PROXIES = * git.{env.DOMAIN} { import accesslog import authelia reverse_proxy 192.168.x.x:3000 # CHANGE IP } # --- Nextcloud --- # In config/config.php: # 'trusted_proxies' => ['172.18.0.0/16'], # 'overwriteprotocol' => 'https', # Plus user_external app + HTTP header auth set to Remote-User. cloud.{env.DOMAIN} { import accesslog import authelia reverse_proxy 192.168.x.x:80 { # CHANGE IP header_up Host {upstream_hostport} } } # --- Paperless-ngx --- # In compose env: # PAPERLESS_ENABLE_HTTP_REMOTE_USER=true # PAPERLESS_HTTP_REMOTE_USER_HEADER_NAME=HTTP_REMOTE_USER paperless.{env.DOMAIN} { import accesslog import authelia reverse_proxy 192.168.x.x:8000 # CHANGE IP } # --- Miniflux --- # In compose env: # AUTH_PROXY_HEADER=Remote-User # AUTH_PROXY_USER_CREATION=true miniflux.{env.DOMAIN} { import accesslog import authelia reverse_proxy 192.168.x.x:8080 # CHANGE IP } # --- BookStack --- # In .env: # AUTH_METHOD=http # HTTP_AUTH_HEADER=Remote-User # HTTP_AUTH_AUTO_INITIATE=true # bookstack.{env.DOMAIN} { # import accesslog # import authelia # reverse_proxy 192.168.x.x:80 # CHANGE IP # } # ============================================================================= # CASE 2b -- App supports OIDC. Authelia acts as the OIDC provider. # # What is OIDC? OpenID Connect is an identity protocol on top of OAuth 2.0. # Authelia becomes the "identity provider" (IdP). Apps redirect users to # auth.DOMAIN, Authelia authenticates them and issues a signed token (JWT), # then redirects back. The app trusts the token instead of checking a password. # # The Caddy config is identical to case 2a: `import authelia` gates the request. # The difference is all on the app side -- it does a token exchange with # Authelia's OIDC endpoint rather than reading a Remote-User header. # # SETUP REQUIRED in authelia/configuration.yml: # Add an identity_providers.oidc block with a client entry for each app. # Each app gets its own client_id and client_secret. # See: https://www.authelia.com/configuration/identity-providers/openid-connect/ # # Result: users never set a password in the app itself. After OIDC is working, # disable all local accounts in the app -- Authelia is the only credential. # ============================================================================= # --- Audiobookshelf --- # Native OIDC support. In Audiobookshelf Settings > Authentication: # Enable OpenID Connect SSO # Issuer URL: https://auth.DOMAIN # Client ID / Secret: from identity_providers.oidc in configuration.yml # Auto Register: on (creates user on first OIDC login) # books.{env.DOMAIN} { # import accesslog # import authelia # reverse_proxy 192.168.x.x:13378 # CHANGE IP # } # --- Jellyfin --- # Requires the community SSO plugin (Jellyfin.Plugin.SSO). # Install from the Plugin Catalogue, then configure OIDC pointing at Authelia. # jellyfin.{env.DOMAIN} { # import accesslog # import authelia # reverse_proxy 192.168.x.x:8096 # CHANGE IP # } # --- Immich --- # No proxy-header auth; OIDC is the only Authelia path. # In Immich Admin > Authentication Settings > OAuth: # Issuer URL: https://auth.DOMAIN # Client ID / Secret: from configuration.yml # Auto register: on # photos.{env.DOMAIN} { # import accesslog # import authelia # reverse_proxy 192.168.x.x:2283 # CHANGE IP # } # --- Mealie --- # OIDC supported. Set in Mealie's admin OIDC settings. # Alternatively, skip OIDC and use case 3 as a simpler gate. # mealie.{env.DOMAIN} { # import accesslog # import authelia # reverse_proxy 192.168.x.x:9000 # CHANGE IP # } # --- Portainer --- # Has OIDC for full SSO -- configure under Settings > Authentication. # Or use case 3 (below) as a simpler gate without OIDC config. # portainer.{env.DOMAIN} { # import accesslog # import authelia # reverse_proxy 192.168.x.x:9000 # CHANGE IP (OIDC version) # } # ============================================================================= # CASE 3 -- App keeps its own login. Authelia adds a 2FA gate in front. # # The user passes Authelia 2FA, then the app's own login appears. # Use for apps that don't support proxy auth headers or OIDC, but you still # want 2FA before they're even reachable. # ============================================================================= # --- Uptime Kuma --- # No proxy auth, no native TOTP -- Authelia is the only way to add 2FA. uptime.{env.DOMAIN} { import accesslog import authelia reverse_proxy 192.168.x.x:3001 # CHANGE IP } # --- Portainer (simple gate, no OIDC) --- portainer.{env.DOMAIN} { import accesslog import authelia reverse_proxy 192.168.x.x:9000 # CHANGE IP } # --- Home Assistant --- # Can also do case 2a via the trusted_networks auth provider + header forwarding. # homeassistant.{env.DOMAIN} { # import accesslog # import authelia # reverse_proxy 192.168.x.x:8123 # CHANGE IP # } # --- Vaultwarden --- # Has its own strong auth + native TOTP. Many skip Authelia here entirely # and rely on Vaultwarden's own 2FA (totally valid). Or use case 3 as an # extra gate if you want 2FA even before the login page loads. # vault.{env.DOMAIN} { # import accesslog # import authelia # reverse_proxy 192.168.x.x:80 # CHANGE IP # } # --- qBittorrent --- # Web UI has its own auth. No proxy headers. # torrent.{env.DOMAIN} { # import accesslog # import authelia # reverse_proxy 192.168.x.x:8080 # CHANGE IP:PORT # } # --- Plex --- # DO NOT use `import authelia` with Plex/Emby. Plex is coupled to plex.tv # cloud auth -- Authelia cannot replace it. More importantly, native clients # (mobile, TV, desktop apps) connect via FQDN through Caddy but CANNOT # complete Authelia's browser-redirect login flow (no cookie, no TOTP prompt). # `import authelia` will break all native clients with a connection error. # # Correct approach: Case 4. Caddy terminates TLS and reverse proxies; Plex's # own token auth handles access control. `import accesslog` keeps fail2ban # watching the subdomain for scanners. # # plex.{env.DOMAIN} { # import accesslog # reverse_proxy 192.168.x.x:32400 # CHANGE IP -- NO import authelia # } # --- Emby --- # Same situation as Plex: native clients go through Caddy but cannot handle # Authelia's login redirect. Use Case 4 -- Caddy + TLS, Emby's own auth. # # emby.{env.DOMAIN} { # import accesslog # reverse_proxy 192.168.x.x:8096 # CHANGE IP -- NO import authelia # } # --- Gotify --- # Notification server. Own auth, no proxy headers. # gotify.{env.DOMAIN} { # import accesslog # import authelia # reverse_proxy 192.168.x.x:8080 # CHANGE IP # } # --- ntfy --- # Notification server. Token-based auth, no proxy headers. # ntfy.{env.DOMAIN} { # import accesslog # import authelia # reverse_proxy 192.168.x.x:80 # CHANGE IP # } # --- wg-easy --- # WireGuard web UI. Own password, no proxy headers. # NOTE: WireGuard clients connect directly to UDP 51820, not through Caddy. # This gate only protects the web management UI. # wg.{env.DOMAIN} { # import accesslog # import authelia # reverse_proxy 192.168.x.x:51821 # CHANGE IP # } # --- Umami --- # Web analytics. Own auth, no proxy headers. # analytics.{env.DOMAIN} { # import accesslog # import authelia # reverse_proxy 192.168.x.x:3000 # CHANGE IP # } # --- phpIPAM --- # IP address management. Own auth, no proxy headers. # ipam.{env.DOMAIN} { # import accesslog # import authelia # reverse_proxy 192.168.x.x:80 # CHANGE IP # } # --- Checkmk --- # Monitoring. Own auth. No proxy headers in the free (Raw) edition. # checkmk.{env.DOMAIN} { # import accesslog # import authelia # reverse_proxy 192.168.x.x:5000 # CHANGE IP # } # --- Snipe-IT --- # Asset management. Own auth, no proxy headers. # assets.{env.DOMAIN} { # import accesslog # import authelia # reverse_proxy 192.168.x.x:80 # CHANGE IP # } # --- Zammad --- # Help desk / ticketing. Own auth, no proxy headers. # tickets.{env.DOMAIN} { # import accesslog # import authelia # reverse_proxy 192.168.x.x:3000 # CHANGE IP # } # --- Lubelog --- # Vehicle maintenance log. Own auth, no proxy headers. # cars.{env.DOMAIN} { # import accesslog # import authelia # reverse_proxy 192.168.x.x:8080 # CHANGE IP # } # --- UniFi Network Application --- # Network controller. Own auth, no proxy headers (LDAP/RADIUS in enterprise). # Serves HTTPS on 8443; skip TLS verify for the upstream. # unifi.{env.DOMAIN} { # import accesslog # import authelia # reverse_proxy 192.168.x.x:8443 { # CHANGE IP # transport http { tls_insecure_skip_verify } # } # } # --- MeshCentral --- # Remote management server. Own auth; has OIDC in paid/enterprise builds. # meshcentral.{env.DOMAIN} { # import accesslog # import authelia # reverse_proxy 192.168.x.x:443 { # CHANGE IP # transport http { tls_insecure_skip_verify } # } # } # ============================================================================= # CASE 4 -- App handles its own auth. Authelia not involved. # Still import accesslog so fail2ban covers this site. # ============================================================================= # --- Syncthing --- # sync.{env.DOMAIN} { # import accesslog # reverse_proxy 192.168.x.x:8384 # CHANGE IP # } # --- Router / NAS admin UI --- # router.{env.DOMAIN} { # import accesslog # reverse_proxy 192.168.x.x:443 { # transport http { tls_insecure_skip_verify } # } # }