Compare commits

..
10 Commits
Author SHA1 Message Date
Claude c8d9c2921d Add handoff doc for "add-site" automation script
Scoped specifically to a fresh session writing a script that automates
the manual two-edit + two-reload workflow for adding a new site behind
Authelia. Covers stack context, file targets, edit recipes per case
(1/2a/2b/3/4), validation hooks, reload order, idempotency
requirements, failure modes, and out-of-scope items. Includes concrete
test fixtures the script should pass.

https://claude.ai/code/session_013XZ1vmgk78k2PEQ5DmJhF3
2026-04-28 02:48:37 +00:00
Claude e2b29d3af9 Enable Authelia template config filter
configuration.yml uses Go-template substitution like
`{{ env "DOMAIN" }}` for the cookie domain, authelia_url, etc.
That substitution doesn't run by default; Authelia reads the
braces literally, fails URL parsing with `invalid character "{"
in host name`, and `domain` validation rejects the hostname for
having no period.

Setting X_AUTHELIA_CONFIG_FILTERS=template enables Authelia's
Go-template filter so the substitution runs at config load.
The env var is inherited by `docker compose run --rm authelia`,
so validate-config picks it up too.

https://claude.ai/code/session_013XZ1vmgk78k2PEQ5DmJhF3
2026-04-26 23:19:22 +00:00
Claude d0dab09e21 Embed paste-ready Caddy snippets next to each access_control case
Each of the four cases in access_control.rules now carries an inline,
clearly-labeled "ALSO PASTE INTO CADDYFILE" block above the
uncommentable Authelia rule, so the user editing configuration.yml
sees both halves of the gate in one place. Case 2a additionally shows
the matching frigate_config/config.yml edit; case 4 has no Authelia
rule but still shows the Caddy block to make the absence explicit.

Caddy blocks use {env.DOMAIN}, Authelia rules use {{ env "DOMAIN" }} --
matching what's already in caddy/snippets.caddyfile and the
configuration.yml header.

https://claude.ai/code/session_013XZ1vmgk78k2PEQ5DmJhF3
2026-04-26 20:48:49 +00:00
Claude 18d5a40d32 Use interactive password prompt for argon2 hash generation
The bootstrap and add-user docs passed --password 'plaintext' on the
docker compose run command line, leaking the plaintext into shell
history (~/.bash_history, ~/.zsh_history) and the process list
(ps aux). Switched all three call sites to the interactive form
(no --password flag), which makes Authelia prompt for the password
and a confirmation without echoing.

Affected:
- README.md "Create your first user" step
- README.md "Add a user" section
- authelia/users_database.yml.example header comment

https://claude.ai/code/session_013XZ1vmgk78k2PEQ5DmJhF3
2026-04-26 20:04:00 +00:00
Claude 628d77563c docs: break first-run into individual copyable steps; clarify two-sided gate
- First-run setup: replace one big bash comment block with numbered
  sub-sections, each step getting its own prose explanation and separate
  code block(s) so individual commands can be copied without selecting
  around surrounding lines
- Step 4: add explicit table showing what happens when each side of the
  Caddy/Authelia gate is missing; add policy comparison table
- Frigate section: move subnet discovery command out of YAML comment
  into its own code block; separate restart commands into own blocks
- Troubleshooting "access denied": show the exact rule YAML to add
  and the restart command, each in their own block
- Troubleshooting "Caddy can't resolve authelia": show full compose
  YAML snippet rather than describing it in prose
- SMTP section: separate each action into its own code block

https://claude.ai/code/session_012eTokAaGiZo7aGt1T2W9BC
2026-04-26 16:43:16 +00:00
Claude 62fb5c75f4 docs: clarify why access_control rules are required per site
default_policy: deny means no rule = 403 even for logged-in users.
Step 4 now explains the two-sided requirement: Caddy controls what
traffic reaches Authelia; the rule controls what Authelia does with it.

https://claude.ai/code/session_012eTokAaGiZo7aGt1T2W9BC
2026-04-26 16:39:18 +00:00
Claude b797602cbc Fix Plex/Emby Authelia caveat: Case 4, not Case 3
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
2026-04-26 15:36:32 +00:00
Claude 6252271816 Expand service list; add OIDC section and Case 2b
- snippets.caddyfile: rename Case 2 → 2a (proxy headers); add Case 2b
  (OIDC) sub-section explaining the token exchange flow, with blocks for
  Audiobookshelf, Jellyfin, Immich, Mealie, Portainer
- snippets.caddyfile: add Case 1 blocks for Prometheus, Alertmanager,
  Gatus, WatchYourLAN, NUT web UI
- snippets.caddyfile: add Case 3 blocks for qBittorrent, Plex (with
  client-bypass caveat), Emby, Gotify, ntfy, wg-easy (with WireGuard
  UDP caveat), Umami, phpIPAM, Checkmk, Snipe-IT, Zammad, Lubelog,
  UniFi, MeshCentral
- README: add OIDC plain-language explanation (what it is, how it differs
  from proxy-header auth, what setup it requires)
- README: expand service table to 30+ entries grouped by case; add ⚠
  caveats for Plex/Emby (clients bypass Caddy)
- README: fix Case 1 header (remove phpIPAM, which has its own auth →
  Case 3); fix stale `caddy/Caddyfile` reference → snippets.caddyfile

https://claude.ai/code/session_012eTokAaGiZo7aGt1T2W9BC
2026-04-26 15:20:06 +00:00
Claude 6cf6a2aeb8 refactor: Caddyfile -> snippets.caddyfile; expand service table; add "should you use Authelia" section
caddy/:
- Rename Caddyfile to snippets.caddyfile -- these are per-service snippets
  to add to an existing Caddyfile, not a replacement for one
- Add per-service example blocks for Homer/Heimdall, Dozzle, Frigate,
  Grafana, Gitea, Nextcloud, Paperless-ngx, Miniflux, BookStack, Immich,
  Uptime Kuma, Portainer, Mealie, Home Assistant, Vaultwarden, Syncthing
  with required per-app config notes inline (DotheEvo style)
- All site blocks use {env.DOMAIN}; only IPs need editing

README.md:
- Add "Should you use Authelia?" section that honestly answers when it
  improves on password-manager + per-service credentials/TOTP, and when
  it's marginal (single user, all services already have native TOTP)
- Expand service compatibility table from 9 to 17 entries with case
  classification (1/2/3/4) and notes for each
- Fix Wire Caddy section to reference snippets.caddyfile
- Fix repo layout tree

https://claude.ai/code/session_012eTokAaGiZo7aGt1T2W9BC
2026-04-26 14:49:06 +00:00
Claude cd78a3560d feat: drive domain from DOMAIN env var -- no manual find-and-replace
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
2026-04-26 14:39:56 +00:00
8 changed files with 1390 additions and 311 deletions
+8
View File
@@ -4,6 +4,14 @@
# so they can be mounted into the container without env-var leakage. This
# .env only holds non-secret tunables.
# ---------------------------------------------------------------------------
# Your root domain. This single value flows into authelia/configuration.yml
# (via Authelia's {{ env "DOMAIN" }} template substitution) and into
# caddy/Caddyfile (via Caddy's {env.DOMAIN} substitution).
# No manual find-and-replace needed -- just set this.
# ---------------------------------------------------------------------------
DOMAIN=example.com
# Pin your image versions. Bump to current stable when you upgrade --
# check https://github.com/authelia/authelia/releases and
# https://github.com/crazy-max/docker-fail2ban/releases.
+456
View File
@@ -0,0 +1,456 @@
# HANDOFF -- script to add a site behind Authelia
This doc is for a fresh session where you'll write a script that
automates the "add a new site behind Authelia" workflow currently done
by hand. Everything below is the context you need to write it
correctly.
## Goal
One command, e.g.
```
./add-site --subdomain doorbell --upstream 192.168.1.60:5555 --case 1 --policy two_factor
```
does all of:
1. Adds the matching rule to `authelia/configuration.yml` under
`access_control.rules:`.
2. Adds (or modifies) the matching site block in the user's Caddyfile.
If a site block already exists with `basic_auth { ... }`, removes
that and inserts `import authelia` instead.
3. Validates Authelia config (`authelia validate-config`).
4. Validates Caddy config (`caddy validate`).
5. Restarts Authelia, then reloads Caddy. Order matters.
6. Optionally `curl`-tests the new URL and reports.
Idempotent: re-running with the same args is a no-op.
## Stack context (what's already running)
- **Authelia 4.39.19**, file backend, SQLite local storage, in-memory
sessions, filesystem notifier. Compose project lives at
`~/docker/authelia/`. Container name `authelia`.
- **fail2ban** as a sidecar in the same compose project. Watches
`./authelia/authelia.log` and `/var/log/caddy/access.log`.
- **Caddy** is in its own compose project at `~/docker/caddy/`
(assumption -- script should accept the path as a parameter).
Container name `caddy`. On the external docker network `caddy_net`.
- Both Caddy and Authelia are on `caddy_net`. Caddy reaches Authelia
as `authelia:9091`.
- The portal is `auth.{DOMAIN}` with `policy: bypass`.
- `default_policy: deny` -- every gated domain MUST have a rule.
- DOMAIN substitution:
- Authelia uses Go templates: `'{{ env "DOMAIN" }}'`. Requires
`X_AUTHELIA_CONFIG_FILTERS=template` env var (already set in the
docker-compose.yml).
- Caddy uses `{env.DOMAIN}`. The Caddy compose passes `DOMAIN`
through to the Caddy container.
## Files the script touches
| File | What lives there | Who edits |
|------|------------------|-----------|
| `~/docker/authelia/authelia/configuration.yml` | `access_control.rules:` list | the script |
| `<user's Caddyfile>` (path: parameter) | site blocks | the script |
| `~/docker/authelia/.env` | `DOMAIN=...`, `TZ=...`, version pins | read-only (script reads DOMAIN from here OR the environment) |
The script does NOT touch:
- `authelia/users_database.yml` (user management is separate)
- `authelia/secrets/*` (manual one-time bootstrap)
- `frigate_config/config.yml` or any other app's own config
(case 2a/2b require app-side edits the script can't safely automate
-- it should print instructions instead)
- DNS, TLS, anything outside the local Caddy + Authelia configs
## Manual workflow the script automates
For reference, here's what a human does today to add `foo.example.com`
as a case-1 site:
```bash
# 1. Edit authelia/configuration.yml -- add under access_control.rules:
# - domain: 'foo.{{ env "DOMAIN" }}'
# policy: 'two_factor'
# 2. Edit your real Caddyfile -- add a new block (or modify existing):
# foo.{env.DOMAIN} {
# import accesslog
# import authelia
# reverse_proxy 192.168.1.60:5555
# }
# 3. Validate before reloading
docker compose -f ~/docker/authelia/docker-compose.yml run --rm authelia \
authelia validate-config --config /config/configuration.yml
docker compose -f ~/docker/caddy/docker-compose.yml exec caddy \
caddy validate --config /etc/caddy/Caddyfile
# 4. Restart Authelia FIRST (so the rule is live before Caddy starts
# forwarding to it -- otherwise default_policy: deny returns 403)
docker compose -f ~/docker/authelia/docker-compose.yml restart authelia
# 5. Reload Caddy (zero-downtime)
docker compose -f ~/docker/caddy/docker-compose.yml exec caddy \
caddy reload --config /etc/caddy/Caddyfile
# 6. Test
curl -sI -o /dev/null -w "%{http_code}\n" https://foo.example.com
# expect 302 redirect to auth.example.com
```
## Cases the script must handle
The full case taxonomy is in `authelia/configuration.yml` and
`caddy/snippets.caddyfile`. Summary:
### Case 1 -- no app auth, Authelia is the only gate
**Authelia rule:**
```yaml
- domain: 'SUBDOMAIN.{{ env "DOMAIN" }}'
policy: 'two_factor' # or one_factor
```
**Caddy block:**
```caddyfile
SUBDOMAIN.{env.DOMAIN} {
import accesslog
import authelia
reverse_proxy UPSTREAM_IP:UPSTREAM_PORT
}
```
Most common case. The migration path from `basic_auth` -- the script
should handle "block exists with basic_auth, swap it for import authelia".
### Case 2a -- app supports trusted-header proxy auth
Same Caddy block as case 1. Same Authelia rule. **Plus** an app-side
config change the script CANNOT safely automate (each app is different:
Frigate's `auth.enabled: False`, Grafana's `[auth.proxy]` section,
Gitea's `ENABLE_REVERSE_PROXY_AUTHENTICATION`, etc.). Script should
print app-specific instructions from a lookup table and require
`--ack-app-config-done` to proceed.
### Case 2b -- app supports OIDC
Same Caddy block as case 1. Same Authelia rule. **Plus** an
`identity_providers.oidc.clients[]` entry to add to
`configuration.yml`. Each app needs its own `client_id`,
`client_secret`, `redirect_uris`, etc.
This is more involved. **Suggested**: out of scope for v1 of the script;
print a pointer to Authelia's OIDC docs and skip.
### Case 3 -- app keeps its own auth, Authelia adds 2FA in front
Caddy block and Authelia rule are identical to case 1. The user just
keeps logging into the app after Authelia. The script doesn't need to
distinguish case 1 from case 3 mechanically -- the only difference is
the user's mental model.
### Case 4 -- no Authelia involvement
```caddyfile
SUBDOMAIN.{env.DOMAIN} {
import accesslog # NO import authelia
reverse_proxy UPSTREAM_IP:UPSTREAM_PORT
}
```
**No Authelia rule.** The script's job is just the Caddy edit + reload.
Useful for things like Plex/Emby where Authelia's redirect breaks
native clients.
## Validation hooks the script must run
In order, before any reload:
1. **Authelia config:**
```bash
docker compose -f ~/docker/authelia/docker-compose.yml run --rm authelia \
authelia validate-config --config /config/configuration.yml
```
Exit 0 = good. Anything else = abort, restore the file from backup.
2. **Caddy config:**
```bash
docker compose -f ~/docker/caddy/docker-compose.yml exec caddy \
caddy validate --config /etc/caddy/Caddyfile
```
Exit 0 = good. Anything else = abort, restore Caddyfile from backup.
After reload:
3. **HTTP probe:**
```bash
curl -sI -o /dev/null -w "%{http_code}\n" https://SUBDOMAIN.DOMAIN
```
- Case 1 / 2a / 2b / 3: expect `302` (redirect to Authelia).
- Case 4: expect `200` or whatever the upstream returns.
- `403` means the Caddy block has `import authelia` but the Authelia
rule isn't in place (or wasn't picked up). Most common script bug.
## Reload semantics
**Order**: Authelia restart, THEN Caddy reload. Reverse order is briefly
broken: Caddy starts forwarding to Authelia for a domain Authelia
doesn't yet have a rule for, and `default_policy: deny` returns 403 to
the user.
**Authelia restart**: full container restart. ~3-5 second outage on
auth.{DOMAIN}. Acceptable for household use; if you want zero-downtime
later, Authelia supports config reload via SIGHUP -- not used here.
**Caddy reload**: `caddy reload` is genuinely zero-downtime. It loads
the new config, validates, and atomically swaps. If validation fails
the old config keeps running.
**Rollback**: if anything fails, the script should revert the file
edits from a `.bak` it took before mutating. Easiest pattern:
```bash
cp authelia/configuration.yml authelia/configuration.yml.bak
cp $CADDYFILE_PATH $CADDYFILE_PATH.bak
# ... edits ...
# if any validate fails:
mv authelia/configuration.yml.bak authelia/configuration.yml
mv $CADDYFILE_PATH.bak $CADDYFILE_PATH
```
## Idempotency
The script must detect and short-circuit when the desired end state
already exists:
- **Authelia rule already present**: parse `access_control.rules:`, look
for an entry with matching `domain:`. If found and policy matches, skip
the YAML edit. If found and policy differs, prompt or fail (don't
silently overwrite).
- **Caddy block already present with `import authelia`**: parse the
Caddyfile, look for `SUBDOMAIN.{env.DOMAIN} {` block. If found and
it has `import authelia`, skip the Caddy edit.
- **Caddy block exists with `basic_auth`**: this is the migration case.
Remove the `basic_auth { ... }` lines, add `import authelia` if
missing. Preserve everything else (transport, header_up, etc).
- **Caddy block exists without auth at all**: just add `import authelia`.
After all checks: if neither file actually changed, skip both reloads
(important -- restarting Authelia for a no-op kicks every active
session).
## Failure modes the script must handle
- `DOMAIN` env var not set / `.env` not present -> abort early with
clear error.
- `caddy_net` docker network doesn't exist -> abort.
- Authelia container not running -> can still edit config and validate;
reload step needs to be skipped or attempted with helpful error.
- Caddy container not running -> same.
- Caddyfile path doesn't exist or isn't writable -> abort.
- The Caddyfile doesn't have `(authelia)` and `(accesslog)` snippets
defined -> the script could either inject them at the top of the file
(fragile) or refuse and tell the user to run a one-time setup step
first. **Recommended**: refuse, with a clear "run `./bootstrap-caddy`
first" message.
- Subdomain conflicts with an existing block that's NOT just a
`basic_auth` migration target (e.g. an entirely different upstream)
-> prompt, don't auto-overwrite.
- A site block exists with `basic_auth` AND something else complicated
(custom matchers, multiple `handle` blocks) -> migration is hard.
Recommended: detect the simple case (single `basic_auth { ... }`
inside the block) and refuse the complex case.
- `validate-config` or `caddy validate` fails -> rollback both files,
report the validator's stderr, exit non-zero.
- HTTP probe fails post-reload -> log the symptom but don't auto-revert;
user may have DNS not pointing yet, etc.
## YAML editing -- preserve comments
`authelia/configuration.yml` has substantial comments (the
"ALSO PASTE INTO CADDYFILE" blocks, case explanations). A naive YAML
round-trip will eat them. Use a comment-preserving library:
- **Python**: `ruamel.yaml` with `YAML(typ='rt')` (round-trip mode).
- **Go**: `gopkg.in/yaml.v3` is comment-aware.
- **`yq`** (the Go-based one from mikefarah): preserves comments
reasonably well for simple ops. Adding a list item:
```bash
yq -i '.access_control.rules += [{"domain": "foo.{{ env \"DOMAIN\" }}", "policy": "two_factor"}]' \
authelia/configuration.yml
```
Note the escaping pain with `{{ env "DOMAIN" }}`. Test before
committing.
The script should anchor inserts at a stable location. The least-bad
anchor is the END of `access_control.rules:` -- always append, never
splice in the middle.
## Caddyfile editing -- there is no good parser
Caddyfile has its own grammar; standard YAML/JSON tools won't touch it.
Options, in order of pragmatism:
1. **Text-based pattern matching** (recommended for v1). Anchors:
- Find `^SUBDOMAIN\.\{env\.DOMAIN\} \{$` to detect existing block.
- For the basic_auth migration: use a small state machine to find
`basic_auth {` ... `}` inside the matched block and delete those
lines, then ensure `import authelia` and `import accesslog` lines
exist.
- For new-block insertion: append at end of file, separated by a
blank line.
2. **`caddy adapt`**: converts Caddyfile to JSON. You could edit the
JSON, then... there's no Caddyfile emitter. Adapt is one-way. Skip.
3. **`caddy fmt`**: normalizes whitespace in a Caddyfile, doesn't
semantically edit. Useful AFTER your edits to clean up.
The text-based approach is fragile for arbitrary Caddyfiles but
predictable for the conventions this stack uses (one site block per
subdomain, snippets imported at top, no exotic matchers in gated
sites).
## Suggested architecture
```
add-site
├── lib/
│ ├── env.sh # find DOMAIN, container names, Caddyfile path
│ ├── yaml_edit.sh # ruamel.yaml or yq wrapper for access_control
│ ├── caddyfile_edit.sh # awk/sed-based site-block patcher
│ ├── validate.sh # authelia + caddy validators
│ └── reload.sh # restart authelia + reload caddy in order
├── cases/
│ ├── case-1.sh # no-app-auth recipe
│ ├── case-2a.md # printable app-side instructions table
│ ├── case-2b.md # OIDC out-of-scope notice + pointer
│ └── case-4.sh # no-Authelia recipe
├── add-site # main entrypoint
└── README.md
```
Or in Python with `ruamel.yaml` and a small Caddyfile patcher class.
Either is fine; the bash version has fewer install steps for an
end-user.
## Concrete test cases the script must pass
Use these as fixtures.
### Test 1: fresh case-1 add
Pre-state:
- `authelia/configuration.yml` has only the `auth.{DOMAIN}` bypass rule.
- Caddyfile has `(authelia)` and `(accesslog)` snippets defined, no
`foo.{env.DOMAIN}` block.
Invocation:
```
./add-site --subdomain foo --upstream 192.168.1.60:5555 --case 1
```
Post-state:
- `access_control.rules:` has new entry for `foo.{{ env "DOMAIN" }}`.
- Caddyfile has new `foo.{env.DOMAIN} { ... }` block with `import
authelia` and `import accesslog`.
- `validate-config` and `caddy validate` both pass.
- `curl -sI https://foo.example.com` returns 302.
### Test 2: idempotent re-run
Run Test 1's invocation twice. Second run: no file edits, no reloads,
exit 0 with "already configured" message.
### Test 3: basic_auth migration
Pre-state:
- Caddyfile has `foo.{env.DOMAIN} { basic_auth { user $2a$... }
reverse_proxy ... }`.
Invocation:
```
./add-site --subdomain foo --case 1 --migrate-basic-auth
```
Post-state:
- The `basic_auth { ... }` lines are gone.
- `import authelia` and `import accesslog` are present.
- `reverse_proxy` line is unchanged.
- Authelia rule added.
### Test 4: validation failure rollback
Pre-state: introduce a typo by hand into the YAML insert template
(e.g. `polciy:` instead of `policy:`). Simulate by mocking the
template.
Expected: `validate-config` fails, both files restored from `.bak`,
non-zero exit, no reload attempted.
### Test 5: case-4 (no Authelia)
Invocation:
```
./add-site --subdomain plex --upstream 192.168.1.5:32400 --case 4
```
Post-state:
- Caddyfile has new `plex.{env.DOMAIN}` block with `import accesslog`,
NO `import authelia`.
- `access_control.rules:` is UNCHANGED.
- Caddy reloads, Authelia is NOT restarted.
## Out of scope (tell the user, don't try to automate)
- DNS A-record creation. Caddy will fail to issue a cert for a domain
that doesn't resolve. Print a "make sure DNS is pointing at this
host" reminder when the script starts.
- TLS / Let's Encrypt failures. Caddy auto-provisions; if it fails,
it's usually DNS or rate-limit. The script should not try to debug.
- App-side proxy-auth config (case 2a). Each app is different. Print
the lookup-table snippet from `caddy/snippets.caddyfile` for that
app and require `--ack-app-config-done` before running.
- OIDC client setup (case 2b). Big enough that it deserves its own
tool. Out of scope.
- User management (`users_database.yml`).
- Secret rotation (`authelia/secrets/*`).
- TOTP enrollment.
## Quick reference: existing files
If you want to read what's there to understand the conventions:
- `docker-compose.yml` -- the auth stack compose, including the
`X_AUTHELIA_CONFIG_FILTERS=template` and `DOMAIN=${DOMAIN}` env vars.
- `authelia/configuration.yml` -- reference for the rule format,
comment style, and the "ALSO PASTE INTO CADDYFILE" blocks under
each case in `access_control.rules:`.
- `caddy/snippets.caddyfile` -- canonical examples of Caddy site
blocks for every case, including app-specific notes the case-2a
table can be extracted from.
- `.env.example` -- the shape of `.env` (DOMAIN, TZ, version pins).
- `README.md` -- the comprehensive bootstrap walkthrough; the
"Adding a new protected site" section is what the script
automates.
## When in doubt
- For YAML edits, dry-run with
`docker compose run --rm authelia authelia config template --config
/config/configuration.yml` -- prints the rendered config so you can
see exactly what Authelia will see.
- For Caddyfile edits, `caddy fmt --overwrite Caddyfile` normalizes
whitespace and `caddy validate` catches syntax errors. Run BOTH
before any reload.
- The single most useful debug command for "why is my site getting
403":
```bash
docker compose -f ~/docker/authelia/docker-compose.yml exec authelia \
tail -f /config/authelia.log
```
Then hit the URL. The log line tells you exactly which rule (or
default_policy) made the call.
+326 -78
View File
@@ -29,7 +29,7 @@ subdomain behind a single sign-on portal at `auth.example.com`.
- Filesystem notifier for password reset (swap to SMTP later, one block change).
- fail2ban bans via the `DOCKER-USER` iptables chain: drops happen at the host
edge before traffic reaches any docker-published port.
- Caddy is not in this stack. Copy `caddy/Caddyfile` into your Caddy setup.
- Caddy is not in this stack. Copy blocks from `caddy/snippets.caddyfile` into your Caddy setup.
## Repo layout
@@ -56,7 +56,7 @@ authelia-stack/
│ └── caddy.local # 30 fails/2 min -> 30 min IP ban
└── caddy/
└── Caddyfile # copy/merge into your Caddy setup
└── snippets.caddyfile # per-service snippets to add to your existing Caddyfile
```
## Prerequisites
@@ -76,42 +76,131 @@ There are four ways a site can relate to Authelia. Pick one per site.
| Case | App has built-in auth? | Supports proxy auth? | What to do |
|------|------------------------|----------------------|------------|
| **1** | No | n/a | `import authelia` in Caddy + rule in Authelia. Authelia is the only login. |
| **2** | Yes | Yes | `import authelia` in Caddy + rule in Authelia + disable app's own login form. Single login. |
| **3** | Yes | No | `import authelia` in Caddy + rule in Authelia. App auth is unchanged. User logs into Authelia then the app. Two logins. |
| **2a** | Yes | Yes (Remote-User header) | `import authelia` + disable app's own login form. Single login via headers. |
| **2b** | Yes | Yes (OIDC) | `import authelia` + configure Authelia as OIDC provider in the app. Single login via token exchange. |
| **3** | Yes | No | `import authelia` in Caddy. App auth is unchanged. User logs into Authelia then the app. Two logins. |
| **4** | Yes | — | Plain `reverse_proxy`. No `import authelia`, no rule. App handles auth. |
Concretely:
- **`doorbell.example.com`** (Pi PTT page) -- **case 1**. No app auth at all.
Authelia is the only gate. Use `two_factor` -- this URL controls a speaker.
- **`cam.example.com`** (Frigate UI) -- **case 2**. Frigate 0.14+ supports
proxy auth. Disable Frigate's login form and let Authelia drive both the
access gate and the role mapping (admin vs. viewer) via headers.
- **`cam.example.com`** (Frigate UI) -- **case 2a**. Frigate 0.14+ supports
proxy auth via `Remote-User` header. Disable Frigate's login form and let
Authelia drive both the access gate and the role mapping (admin vs. viewer).
- **`books.example.com`** (Audiobookshelf) -- **case 2b**. App redirects to
Authelia, Authelia issues a JWT token, app accepts it. No password set in
the app itself.
- **Router admin / NAS UI** -- **case 3** if you want a 2FA gate in front,
**case 4** if you just leave it to the app.
### What is OIDC?
OpenID Connect (OIDC) is an identity protocol layered on top of OAuth 2.0.
The short version: instead of an app checking your password itself, it
redirects you to Authelia, Authelia authenticates you and issues a signed
token (JWT), and the app trusts that token. The app never handles your
password — it only ever sees the token.
Authelia becomes the **identity provider** (IdP). Apps like Audiobookshelf,
Immich, Jellyfin, and Mealie become **relying parties** — they trust Authelia's
tokens and use them to identify users.
The practical difference from proxy-header auth (case 2a):
- **Headers**: Caddy adds `Remote-User` to every request and the app reads it.
Works silently. Requires the app to support header-based auth.
- **OIDC**: The browser does a full redirect dance (app → Authelia → app).
Users see the Authelia login page. Requires the app to support OIDC/OAuth.
Both result in the same thing: one Authelia credential covers the app.
OIDC requires additional setup in `authelia/configuration.yml`
an `identity_providers.oidc` block with a client entry per app, each with
its own `client_id` and `client_secret`. See Authelia's OIDC docs for the
full config. The Caddy side is identical to case 2a: `import authelia`.
Default policy in `configuration.yml` is `deny`, so a domain with no rule
AND no `import authelia` in Caddy never reaches Authelia at all.
### Should you use Authelia at all?
A password manager with per-service credentials and per-service TOTP is a
solid security posture. Authelia improves on it in specific situations:
| Situation | Password manager alone | Authelia |
|-----------|----------------------|----------|
| App has **no auth at all** (Homer, Dozzle, doorbell page) | Can't help | Gates it with 2FA, zero app changes |
| App has auth but **no native TOTP** (Uptime Kuma, Syncthing, phpIPAM) | Password-only | Adds 2FA in front for free |
| Multiple people need access | Change credentials in N places | Disable one account in Authelia |
| True SSO -- log in once, reach 10 services | Still authenticates 10× (autofilled) | One session covers all gated services |
| Consistent rate-limiting / brute-force protection | Wildly varies per app | fail2ban + regulation applied uniformly |
**Where Authelia is marginal for a single user:**
- All your services already have native TOTP support → Authelia adds mostly
friction. You're right that 2FA doesn't require Authelia: Vaultwarden,
Nextcloud, Grafana, and Gitea all support TOTP natively. If you've already
set that up in a manager like Bitwarden, Authelia's 2FA argument is weaker.
- The Authelia password itself becomes keys-to-the-kingdom for everything
gated behind it, which is why the TOTP requirement on Authelia matters more
than on any individual service.
**The real sweet spot:**
- You have services with zero auth (case 1: Homer, Prometheus, NUT web UI,
Gatus) -- something has to gate them.
- You have services with auth but no TOTP -- Authelia gives them 2FA without
touching the app at all.
- You manage access for more than one person.
### How to tell if an app supports proxy auth (case 2)
Look for any of these in the app's docs:
Look for any of these in the app's docs: "Remote-User header", "trusted
upstream", "trusted proxies", "header-based auth", "SSO via reverse proxy".
- "Remote-User header", "trusted upstream", "trusted proxies"
- "Header-based authentication", "SSO via reverse proxy"
- Support for `X-Forwarded-User`, `X-Remote-User`, or `Remote-User`
| App | Proxy auth? | Notes |
|-----|-------------|-------|
| Frigate 0.14+ | Yes | `auth.enabled: False` + `proxy:` block in config.yml |
| Grafana | Yes | `[auth.proxy]` section in grafana.ini |
| Gitea / Forgejo | Yes | `REVERSE_PROXY_AUTHENTICATION_USER` in app.ini |
| Nextcloud | Yes | `TRUSTED_PROXIES` env + `overwriteprotocol = https` |
| Home Assistant | Yes | `trusted_networks` auth provider + `use_x_forwarded_for` |
| Jellyfin | Partial | Community plugin required |
| Portainer | No | Use Authelia OIDC integration instead |
| Vaultwarden | No | Use Authelia OIDC integration instead |
| Router/NAS admin | Rarely | Use case 3 (2FA gate) or case 4 |
| App | Case | Notes |
|-----|------|-------|
| **Proxy-header auth (case 2a)** | | |
| Frigate 0.14+ | 2a | `auth.enabled: False` + `proxy:` block in config.yml |
| Grafana | 2a | `[auth.proxy]` in grafana.ini; `GF_AUTH_PROXY_ENABLED=true` |
| Gitea / Forgejo | 2a | `ENABLE_REVERSE_PROXY_AUTHENTICATION=true` in app.ini |
| Nextcloud | 2a | `trusted_proxies` + `user_external` app + HTTP header auth |
| Paperless-ngx | 2a | `PAPERLESS_ENABLE_HTTP_REMOTE_USER=true` |
| Miniflux | 2a | `AUTH_PROXY_HEADER=Remote-User` env var |
| Home Assistant | 2a | `trusted_networks` auth provider + header forwarding |
| BookStack | 2a | `AUTH_METHOD=http` + `HTTP_AUTH_HEADER=Remote-User` in .env |
| **OIDC auth (case 2b)** | | |
| Audiobookshelf | 2b | Native OIDC; configure in Settings > Authentication |
| Jellyfin | 2b | Requires `Jellyfin.Plugin.SSO` from Plugin Catalogue |
| Immich | 2b | No header auth; OIDC only. Admin > OAuth settings |
| Mealie | 2b | OIDC supported; or use case 3 as a simpler gate |
| Portainer | 2b | OIDC in Settings > Authentication; or case 3 as simple gate |
| **No built-in auth (case 1)** | | |
| Homer / Heimdall | 1 | No auth at all -- Authelia is the only gate |
| Dozzle | 1 | No auth by default -- Authelia is the only gate |
| Prometheus | 1 | No auth built in; always gate, metrics expose internals |
| Alertmanager | 1 | No auth built in |
| Gatus | 1 | Status page; optional built-in OIDC but simpler to gate here |
| WatchYourLAN | 1 | Network ARP scanner, no built-in auth |
| NUT web UI | 1 | NUT daemon has no web UI; frontend web apps vary -- most have no auth |
| **App keeps own auth (case 3)** | | |
| Uptime Kuma | 3 | No proxy auth, no native TOTP -- Authelia is the only way to add 2FA |
| qBittorrent | 3 | Web UI has own auth; no proxy headers |
| Plex | 4 ⚠ | Do NOT use `import authelia` -- native clients go through Caddy but cannot complete browser-redirect auth; use Case 4, Plex handles its own auth |
| Emby | 4 ⚠ | Same as Plex |
| Gotify | 3 | Notification server; own auth |
| ntfy | 3 | Notification server; token-based auth |
| wg-easy | 3 | WireGuard web UI; WireGuard clients bypass Caddy (UDP 51820) |
| Umami | 3 | Analytics; own auth |
| phpIPAM | 3 | IP address management; own auth |
| Checkmk | 3 | Monitoring; own auth (LDAP in enterprise edition) |
| Snipe-IT | 3 | Asset management; own auth |
| Zammad | 3 | Help desk / ticketing; own auth |
| Lubelog | 3 | Vehicle maintenance; own auth |
| UniFi | 3 | Network controller; own auth |
| MeshCentral | 3 | Remote management; own auth; OIDC in enterprise builds |
| Vaultwarden | 3 or 4 | Strong native auth + TOTP; many skip Authelia here entirely |
| Router / NAS admin | 3 or 4 | Depends on firmware; case 4 is usually fine |
| **App handles own auth (case 4)** | | |
| Syncthing | 4 | Decent native auth; proxy auth not supported |
## Getting git and authenticating to GitHub
@@ -192,73 +281,186 @@ gh auth login
## First-run setup
### 0. Clone the repo
The Frigate stack lives on `main`. This auth stack is on the `authelia` branch -- clone it separately into its own directory.
```bash
git clone -b authelia \
https://github.com/outis1one/frigate_w_audio.git \
~/docker/authelia
```
```bash
# 0) Clone the auth stack onto the server.
# (The Frigate stack lives on the `main` branch and is cloned separately.)
gh repo clone outis1one/frigate_w_audio -- \
--branch authelia ~/docker/authelia
cd ~/docker/authelia
```
# 1) Create the external docker network (Caddy must also be on this).
### 1. Create the external Docker network
Caddy must join this same network so it can reach Authelia by container name. Skip if `caddy_net` already exists.
```bash
docker network create caddy_net 2>/dev/null || true
```
# 2) Bootstrap the secrets directory.
### 2. Generate secrets
Authelia loads these from files so they never appear in `docker inspect` or process listings.
```bash
mkdir -p authelia/secrets
openssl rand -hex 32 > authelia/secrets/JWT_SECRET
openssl rand -hex 32 > authelia/secrets/SESSION_SECRET
openssl rand -hex 32 > authelia/secrets/STORAGE_ENCRYPTION_KEY
chmod 600 authelia/secrets/*
```
# 3) Copy and edit .env.
### 3. Set your domain
`DOMAIN` is the only value you set here. It flows into `authelia/configuration.yml` via Go template substitution and into your Caddyfile via `{env.DOMAIN}` -- no find-and-replace needed anywhere else.
```bash
cp .env.example .env
$EDITOR .env # set TZ; pin AUTHELIA_VERSION if you want
```
# 4) Edit authelia/configuration.yml.
# Replace every `example.com` with your real root domain.
# Look for the four CHANGE comments: totp.issuer, session.cookies[].domain,
# session.cookies[].authelia_url, session.cookies[].default_redirection_url.
# Also uncomment access_control.rules entries for the sites you want to gate.
```bash
$EDITOR .env
```
Set `DOMAIN=yourdomain.com` and `TZ=Your/Timezone`. Save and close.
### 4. Add access control rules
**This step and step 8 (Caddy wiring) must be done together for every site you want to gate. Both are required -- neither alone is enough.**
```bash
$EDITOR authelia/configuration.yml
```
# 5) Create your first user.
Scroll to `access_control.rules`. Uncomment the rule for each site you want to protect and choose a policy:
```yaml
- domain: 'cam.{{ env "DOMAIN" }}'
policy: 'two_factor'
```
#### Why both sides are required
Caddy and Authelia each control one half of the gate:
| What you configure | What it does |
|--------------------|-------------|
| `import authelia` in a Caddy site block | Sends that site's requests to Authelia for a decision |
| Rule in `access_control.rules` | Tells Authelia what decision to make |
The default policy is `deny`. If a request reaches Authelia with no matching rule, it gets a **403 Forbidden -- no login prompt, no redirect, just blocked**. This is true even for an already-logged-in user.
Miss either side and here is what happens:
| Caddy `import authelia` | Rule in `configuration.yml` | Result |
|------------------------|----------------------------|--------|
| Missing | Present | Site is open -- Authelia is never consulted |
| Present | Missing | 403 Forbidden, no login prompt |
| Both missing | | Site is open -- Authelia is never consulted |
| Both present | | Works correctly |
#### Which policy to use
| Policy | Requires |
|--------|---------|
| `bypass` | Nothing -- Authelia waves the request through. Used for the portal itself only. |
| `one_factor` | Password only |
| `two_factor` | Password + TOTP. Use this for everything. |
### 5. Create your first user
```bash
cp authelia/users_database.yml.example authelia/users_database.yml
$EDITOR authelia/users_database.yml # set username, email, displayname
```
# Generate the password hash:
```bash
$EDITOR authelia/users_database.yml
```
Fill in `username`, `email`, and `displayname`. Then generate the password hash:
```bash
docker compose run --rm authelia \
authelia crypto hash generate argon2 --password 'your-real-password'
# Paste the $argon2id$... output into the password: field.
authelia crypto hash generate argon2
```
# 6) Pre-create the Authelia log file.
# Docker creates a DIRECTORY at the bind-mount path if the file doesn't
# exist, which breaks fail2ban's mount. Create it as an empty file first.
Authelia prompts for the password and a confirmation without echoing --
the plaintext never hits your shell history or `ps aux`. Copy the
`Digest: $argon2id$...` line from the output and paste the digest
(everything from `$argon2id` onward) as the `password:` value in
`users_database.yml`.
### 6. Pre-create the Authelia log file
Docker creates a **directory** at a bind-mount path if the source file does not exist yet. That breaks fail2ban's read-only mount. Create it as an empty file first:
```bash
touch authelia/authelia.log
```
# 7) Validate config before starting.
### 7. Validate the config
```bash
docker compose run --rm authelia \
authelia validate-config --config /config/configuration.yml
# Expect: "Configuration: validation complete" with no errors.
# 8) Wire up Caddy (see "Wire Caddy into Authelia" below).
# 9) Bring it up.
docker compose up -d
docker compose logs -f authelia # expect "Authelia is listening on ..."
docker compose logs -f fail2ban # expect "Jail authelia is now active"
```
Expect: `Configuration: validation complete` with no errors. Fix any YAML issues before continuing.
### 8. Wire Caddy
See [Wire Caddy into Authelia](#wire-caddy-into-authelia) below. Add the Caddy site block for each site alongside the rule you added in step 4.
### 9. Bring it up
```bash
docker compose up -d
```
Confirm both services started cleanly:
```bash
docker compose logs -f authelia
```
Expect: `Authelia is listening on ...`
```bash
docker compose logs -f fail2ban
```
Expect: `Jail authelia is now active`
## Wire Caddy into Authelia
Open `caddy/Caddyfile`. It defines:
Open `caddy/snippets.caddyfile`. It contains copy-paste blocks for your
existing Caddyfile, not a replacement for it:
- `(authelia)` -- reusable snippet: add `import authelia` to any site block.
- `(accesslog)` -- writes Caddy's JSON access log to `/var/log/caddy/access.log`
so fail2ban's `caddy-4xx` jail can watch it.
- `auth.example.com` -- the Authelia portal.
- Example site blocks for all four cases (cases 1-3 active, case 4 commented).
- `(authelia)` and `(accesslog)` snippet definitions -- paste once near the
top of your Caddyfile.
- `auth.{env.DOMAIN}` -- the Authelia portal block.
- Per-service examples for all four cases (Frigate, Grafana, Gitea,
Uptime Kuma, Homer, etc.) with the required per-app config notes inline.
Copy the relevant blocks into your real Caddyfile, replace `example.com` with
your domain and `192.168.x.x` with real upstream IPs, then reload Caddy.
Copy the blocks you need into your real Caddyfile and replace `192.168.x.x`
with real upstream IPs. The domain is already templated as `{env.DOMAIN}` --
just make sure the `DOMAIN` environment variable is available to Caddy:
```bash
# Dockerized Caddy -- add to its .env or compose environment:
DOMAIN=yourdomain.com
# System Caddy -- add to /etc/caddy/caddy.env (or wherever systemd reads env):
echo 'DOMAIN=yourdomain.com' | sudo tee -a /etc/caddy/caddy.env
# Then make sure the systemd unit loads it:
# EnvironmentFile=/etc/caddy/caddy.env (in the [Service] section)
sudo systemctl daemon-reload && sudo systemctl reload caddy
```
**Every** site block should have `import accesslog` -- even case 4 sites.
fail2ban's caddy-4xx jail watches the one log file and covers all your
@@ -287,12 +489,19 @@ sudo chown caddy:caddy /var/log/caddy # adjust to your Caddy UID
Edit `frigate_config/config.yml` in your Frigate stack:
First, find your `caddy_net` subnet -- you need this for `trusted_proxies`:
```bash
docker network inspect caddy_net | jq '.[0].IPAM.Config'
```
Then edit `frigate_config/config.yml`:
```yaml
auth:
enabled: False
trusted_proxies:
- 172.18.0.0/16 # the caddy_net subnet -- find it with:
# docker network inspect caddy_net | jq '.[0].IPAM.Config'
- 172.18.0.0/16 # replace with your caddy_net subnet from above
proxy:
header_map:
@@ -301,21 +510,28 @@ proxy:
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` in caddy/Caddyfile.
# auth_secret: 'your-32-byte-hex'
```
Then uncomment `cam.example.com` in `authelia/configuration.yml`, restart
both services:
To generate the optional `auth_secret`:
```bash
openssl rand -hex 32
```
Set the same value in the `header_up X-Proxy-Secret` line in your Caddy site block.
Add the rule to `authelia/configuration.yml` (step 4 of first-run), then restart both services:
```bash
docker compose restart authelia
```
```bash
docker compose restart frigate # in your Frigate stack
```
Verify: `https://cam.example.com` in a private window goes to Authelia and
back without a Frigate login screen.
Verify in a private browser window: `https://cam.yourdomain.com` should go to Authelia and back without a Frigate login screen.
## First login + TOTP enrollment
@@ -339,9 +555,10 @@ back without a Frigate login screen.
Append to `authelia/users_database.yml`, generate a hash:
```bash
docker compose run --rm authelia \
authelia crypto hash generate argon2 --password 'new-password'
authelia crypto hash generate argon2
```
Paste the hash as `password:`. Restart or wait 5 minutes for auto-reload.
Authelia prompts for the password (no echo, not in shell history). Paste
the printed digest as `password:`. Restart or wait 5 minutes for auto-reload.
### Disable a user
@@ -436,18 +653,26 @@ notifier:
# password loaded via AUTHELIA_NOTIFIER_SMTP_PASSWORD_FILE
```
Add the secret and wire it up:
Add the secret file:
```bash
echo 'your_smtp_password' > authelia/secrets/SMTP_PASSWORD
chmod 600 authelia/secrets/SMTP_PASSWORD
```
Add to the authelia service environment in `docker-compose.yml`:
Add to the `authelia` service `environment:` block in `docker-compose.yml`:
```yaml
- AUTHELIA_NOTIFIER_SMTP_PASSWORD_FILE=/secrets/SMTP_PASSWORD
```
Restart and look for `"Notifier SMTP startup check successful"` in logs.
Restart and check for a successful startup message:
```bash
docker compose restart authelia && docker compose logs -f authelia
```
Expect: `Notifier SMTP startup check successful`
## Security notes
@@ -472,8 +697,18 @@ of it served over HTTPS. Mixed HTTP/HTTPS won't work; the session cookie is
### "access denied" with no login prompt
`default_policy: deny` and no `access_control` rule for this domain. Add a
rule in `authelia/configuration.yml` and restart Authelia.
`default_policy: deny` -- a request reached Authelia with no matching rule for that domain. Add a rule under `access_control.rules` in `authelia/configuration.yml`:
```yaml
- domain: 'yoursite.{{ env "DOMAIN" }}'
policy: 'two_factor'
```
Then restart Authelia:
```bash
docker compose restart authelia
```
### Authelia container restarts forever
@@ -486,8 +721,21 @@ Most often: missing/empty secret files in `authelia/secrets/`, bad YAML in
### Caddy can't resolve `authelia`
Caddy isn't on `caddy_net`. Add `networks: [caddy_net]` to your Caddy
service and `caddy_net: external: true` at the bottom of its compose, then:
Caddy isn't on `caddy_net`. Add this to your Caddy service in its compose file:
```yaml
services:
caddy:
networks:
- caddy_net
networks:
caddy_net:
external: true
```
Then recreate the Caddy container:
```bash
docker compose up -d caddy
```
+100 -27
View File
@@ -11,6 +11,10 @@
# 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
###############################################################################
@@ -37,7 +41,7 @@ identity_validation:
totp:
disable: false
issuer: 'example.com' # CHANGE: your root domain (shown in authenticator app)
issuer: '{{ env "DOMAIN" }}' # shown in your authenticator app
algorithm: 'sha1'
digits: 6
period: 30
@@ -75,8 +79,6 @@ authentication_backend:
# ---------------------------------------------------------------------------
# WHICH SITES NEED A RULE HERE?
#
# There are four ways a site can relate to Authelia:
#
# 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.
@@ -104,43 +106,114 @@ access_control:
rules:
# The Authelia portal itself is always bypass.
- domain: 'auth.example.com' # CHANGE
- domain: 'auth.{{ env "DOMAIN" }}'
policy: 'bypass'
# -------------------------------------------------------------------
# CASE 1: No app auth -- Authelia is the only gate.
# The Pi doorbell PTT page has no built-in authentication.
# two_factor is appropriate -- this URL controls a speaker in your house.
# -------------------------------------------------------------------
# - domain: 'doorbell.example.com' # CHANGE
# ===================================================================
# 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 2: App supports trusted-header proxy auth -- replace app login.
# Frigate 0.14+: set `auth.enabled: False` and configure `proxy:` in
# frigate_config/config.yml (see README.md "Switching Frigate to Authelia").
# Single login: Authelia authenticates, Frigate reads Remote-User/Groups.
# -------------------------------------------------------------------
# - domain: 'cam.example.com' # CHANGE
# ===================================================================
# 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 3: App keeps its own auth; Authelia adds a 2FA gate in front.
# The app's login form is still shown after Authelia passes the request.
# User logs into Authelia (2FA) then into the app separately.
# -------------------------------------------------------------------
# - domain: 'nas.example.com' # CHANGE/REMOVE example
# ===================================================================
# 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 4: No rule here, no `import authelia` in Caddy. App handles auth.
# ===================================================================
# 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: 'example.com' # CHANGE: your root domain
authelia_url: 'https://auth.example.com' # CHANGE
default_redirection_url: 'https://example.com' # CHANGE
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'
+7 -3
View File
@@ -2,13 +2,17 @@
###############################################################################
# Authelia users database
#
# Copy this to users_database.yml (gitignored) and edit. Generate the
# Copy this to users_database.yml (gitignored) and edit. Generate each
# password hash with:
#
# docker compose run --rm authelia \
# authelia crypto hash generate argon2 --password 'your-plaintext-pass'
# authelia crypto hash generate argon2
#
# Authelia prompts for the password and a confirmation without echoing,
# so the plaintext never hits shell history or `ps aux`. Output ends
# with `Digest: $argon2id$v=19$m=...`. Paste the digest (everything from
# `$argon2id` onward) as the `password:` value below.
#
# Paste the resulting `$argon2id$v=19$m=...` string as the `password:` value.
# Restart Authelia for changes to take effect (or wait refresh_interval).
###############################################################################
-203
View File
@@ -1,203 +0,0 @@
# =============================================================================
# Caddyfile -- Authelia + fail2ban integration
#
# Copy this file into your Caddy setup (or merge the relevant blocks into
# your existing Caddyfile), edit all placeholders, 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
#
# Placeholders to replace:
# example.com -> your real root domain
# 192.168.x.x -> real upstream 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.example.com { # CHANGE
import accesslog
reverse_proxy authelia:9091
}
# =============================================================================
# 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 add (or uncomment) the doorbell.example.com rule in configuration.yml.
# =============================================================================
# doorbell.example.com { # CHANGE
# 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 {
# transport http {
# read_timeout 60s
# write_timeout 60s
# }
# }
# }
#
# handle {
# reverse_proxy 192.168.x.x:5555
# }
# }
# =============================================================================
# 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.example.com rule in authelia/configuration.yml
# and restart: docker compose restart authelia (in the authelia stack)
# docker compose restart frigate (in the camera stack)
# =============================================================================
cam.example.com { # CHANGE
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 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.example.com'
# policy: 'two_factor'
# =============================================================================
# nas.example.com { # 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.example.com { # CHANGE/REMOVE example
# import accesslog
# reverse_proxy 192.168.x.x:PORT { # CHANGE
# transport http {
# tls_insecure_skip_verify
# }
# }
# }
+484
View File
@@ -0,0 +1,484 @@
# =============================================================================
# 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 }
# }
# }
+9
View File
@@ -34,6 +34,15 @@ services:
- AUTHELIA_SESSION_SECRET_FILE=/secrets/SESSION_SECRET
- AUTHELIA_STORAGE_ENCRYPTION_KEY_FILE=/secrets/STORAGE_ENCRYPTION_KEY
- TZ=${TZ:-UTC}
# Enables Go-template substitution in configuration.yml so
# `{{ env "DOMAIN" }}` actually expands instead of being read as a
# literal string. Without this, Authelia parses the braces as part
# of the hostname and validate-config fails with `invalid character
# "{" in host name`. Inherited by `docker compose run --rm authelia`,
# so validate-config picks it up too.
- X_AUTHELIA_CONFIG_FILTERS=template
# Passed through so authelia/configuration.yml can use {{ env "DOMAIN" }}.
- DOMAIN=${DOMAIN}
volumes:
- ./authelia:/config
- ./authelia/secrets:/secrets:ro