Native clients (mobile, TV, desktop) DO route through Caddy via FQDN, but cannot complete Authelia's browser-redirect login flow -- they get a 302 to auth.DOMAIN and fail. `import authelia` breaks all native clients. Correct setup is Case 4: Caddy terminates TLS, import accesslog for fail2ban, NO import authelia. Plex/Emby token auth handles access control. https://claude.ai/code/session_012eTokAaGiZo7aGt1T2W9BC
485 lines
15 KiB
Caddyfile
485 lines
15 KiB
Caddyfile
# =============================================================================
|
|
# 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 }
|
|
# }
|
|
# }
|