Caddy bringt deine Apps öffentlich ans Netz, der Mail-Relay schickt Notifications raus. Was fehlt: ein Login davor. Für Apps wie Vaultwarden oder Immich willst du nicht den App-eigenen Login als einzige Hürde haben – du willst eine Schicht davor, die schon vor dem ersten App-Aufruf prüft, ob hier überhaupt jemand reinkommen darf. Und das nicht nur mit einem Passwort, sondern mit 2FA (Zwei-Faktor-Authentifizierung): zusätzlich zum Passwort ein zweiter Faktor, typischerweise ein 6-stelliger Code, der sich alle 30 Sekunden ändert und nur auf deinem Handy lebt. Selbst wenn dein Passwort geleakt wird, kommt niemand ohne dein Handy rein.

Genau das macht Authelia. Eine kleine, ressourcenarme Anwendung, die zwischen Caddy und deinen persönlichen Apps sitzt und entscheidet: gültiger Login mit zweitem Faktor? → weiter zur App. Sonst → Login-Portal. Eine einzige Session deckt alle deine geschützten Apps ab – einmal eingeloggt, kommst du bis zum Cookie-Ablauf überall rein, ohne dich jedes Mal erneut anmelden zu müssen.

Wichtig vorab: Authelia ist der Kontrolleur an der Tür, kein Ersatz für den Login der App selbst. Caddy ist die einzige Eingangstür – aber er prüft nicht, wer reinkommt. Genau das macht Authelia: Du meldest dich an, Authelia entscheidet, ob du weiter darfst. Danach fragen Vaultwarden, Immich oder Paperless trotzdem nochmal nach ihrem eigenen Account. Klingt zunächst nach doppelter Arbeit, ist aber genau das Richtige: Die App-Logins bleiben App-Logins (mit ihren eigenen Rollen, Berechtigungen, Datenbanken), und Authelia stellt davor sicher, dass überhaupt nur angemeldete Nutzer die App zu sehen bekommen.

Am Ende hast du

  • Authelia + Redis laufen als UI-Defined Stack in Komodo
  • Eine eigene Login-Seite unter auth.deinedomain.de, geschützt mit HTTPS
  • Einen ersten Admin-User mit einem starken Passwort
  • 2FA eingerichtet: Beim Login musst du zusätzlich einen 6-stelligen Code aus einer Authenticator-App eingeben
  • Authelia kann Mails verschicken – z. B. für „Passwort vergessen"-Funktionen
  • Einmal eingeloggt bleibst du eingeloggt, auch nach Updates oder Neustarts
  • Authelia-Logs werden als Datei mitgeschrieben – die brauchen wir später für CrowdSec
  • In Caddy reicht künftig eine Zeile pro App, um sie hinter den Login zu hängen
  • Eine durchdachte Schutz-Strategie: Standard-Schutz für alle Apps, höchste Stufe für die wirklich sensiblen

Voraussetzungen

  • Artikel 02–08 abgeschlossen
  • Caddy läuft (Artikel 07) – inkl. Wildcard-DNS, dadurch ist auth.deinedomain.de schon vorbereitet
  • Mail-Relay läuft (Artikel 08) – Authelia verschickt Account-Setup-Mails darüber
  • Komodo läuft, Server vps-homelab ist eingebunden
  • Eine TOTP-App auf dem Smartphone (Empfehlung wie in Artikel 05: 2FAS Auth, Aegis oder Bitwarden Authenticator)

1. Was Authelia für dieses Setup leistet

In Stichpunkten:

  • Ein Login für alles – einmal an auth.deine-domain.de einloggen, danach gelten die Cookies für alle Subdomains von deine-domain.de. Vaultwarden, Immich, Paperless – alle bekommen denselben Login-Status.
  • 2FA per Code aus der Authenticator-App – zweiter Faktor zusätzlich zum Passwort. Pflicht oder optional je nach App.
  • Schutz vor automatischen Login-Versuchen – nach mehreren Fehlversuchen wird der Account für eine konfigurierbare Zeit gesperrt. Das bremst Bots aus, die Passwörter durchprobieren.
  • Self-Service – Passwort-Reset per E-Mail (über den Mail-Relay), 2FA-Setup im User-Portal.
  • Persistente Sessions – durch Redis als Session-Store überleben Logins auch Stack-Updates und Container-Restarts.

Was Authelia nicht macht: den App-Login ersetzen. Wenn du dich bei Authelia eingeloggt hast und Vaultwarden öffnest, fragt Vaultwarden trotzdem nochmal nach seinem eigenen Master-Passwort. Authelia entscheidet nur: Darf diese Person die App überhaupt sehen? Was innerhalb der App passiert, regelt die App selbst.

Für die Apps in Phase 3 (Vaultwarden, Immich, Paperless) ist das genau das richtige Modell. Sie haben jeweils ihren eigenen Login; Authelia liefert die vorgelagerte Schutzschicht.

2. So funktioniert Forward Auth

Wenn du https://vault.deinedomain.de aufrufst, passiert das hier:

1. Browser → Caddy:           GET https://vault.deinedomain.de/
2. Caddy → Authelia:          GET /api/authz/forward-auth (im internen Netz)
3. Authelia prüft die Cookies
4a. Cookie gültig + 2FA OK:   200 OK + User-Header
4b. Cookie fehlt/ungültig:    301 Redirect zu auth.deinedomain.de
5a. Caddy bei 200 OK:         reverse_proxy zur App, User-Header dran
5b. Caddy bei Redirect:       Browser landet auf Login-Portal
6. Nach Login:                Authelia setzt Cookie + Redirect zurück zur App

Das Schöne: Vaultwarden, Immich oder Paperless wissen nicht, dass Authelia existiert. Sie sehen normale HTTP-Anfragen, ggf. mit Headern wie Remote-User und Remote-Email zur Identifikation. Mehr nicht. Du kannst Authelia jederzeit vor jede beliebige Web-App schalten, solange Caddy davor sitzt.

Caddy spricht Authelia ausschließlich intern über das homelab_net an – http://authelia:9091. Authelia veröffentlicht keine Ports am Host, genau wie Mail-Relay und Komodo-Periphery.

3. Access-Control-Strategie: pragmatisch statt paranoid

Authelia kennt vier Policy-Stufen pro Domain/Pfad:

Policy Bedeutung
deny Zugriff komplett blockiert
bypass Authelia greift nicht ein, App-Login allein
one_factor Passwort allein reicht
two_factor Passwort + TOTP/WebAuthn

Die Strategie für diese Serie

default_policy: one_factor

→ two_factor + group:admins  für sensible Apps (Vaultwarden, Immich, Paperless, File Browser)
→ two_factor                 für persönliche Apps ohne erhöhte Anforderungen
→ bypass                     für die Auth-Subdomain selbst

Warum one_factor als Default und nicht deny?

Beide funktionieren grundsätzlich, aber one_factor ist als Sicherheitsnetz pragmatischer:

  • Wenn du eine neue Subdomain in Caddy einbaust und import auth setzt, aber vergisst, eine access_control-Regel zu ergänzen → mit default_policy: deny bekommst du 401 statt eines Login-Formulars. Du musst Authelia anfassen, bevor irgendwer reinkommt.
  • Mit default_policy: one_factor ist die unbekannte Subdomain sofort mit Passwort-Login geschützt. Du kannst in Ruhe entscheiden, ob sie two_factor braucht – aber sie hängt nie ungeschützt im Netz.
  • Wirklich öffentliche Subdomains (Ghost-Blog) haben in Caddy ohnehin kein import auth – die fragen Authelia nie. Die Default-Policy ist also nur das Fallback für Subdomains, die durch Authelia gehen.

Zweiter Filter: subject: ["group:admins"]

Für die wirklich sensiblen Apps reicht two_factor allein nicht aus. Wir verlangen zusätzlich, dass der eingeloggte User in der Gruppe admins ist:

- domain: 'vault.deine-domain.de'
  policy: 'two_factor'
  subject: ['group:admins']

Was das bringt: Selbst wenn du später Family-Member oder Freunde als User in users_database.yml hinzufügst (z. B. für Outline oder Immich-Sharing), kommen die ohne admins-Mitgliedschaft nicht auf Vaultwarden oder Paperless – auch nicht mit gültigem Passwort + 2FA.

Bei nur einem User

Wenn du gerade Single-User unterwegs bist, ändert der group:admins-Filter nichts an deinem Erlebnis – du bist ja in admins. Aber wenn du das Pattern von Anfang an mitnimmst, brauchst du beim ersten Zweit-User keine Konfigurations-Migration. Kostet jetzt nichts, schützt später konsequent.

Empfohlene Vergabe in dieser Serie

App Policy Begründung
Vaultwarden (Art 12) two_factor + group:admins Master-Passwörter – nichts Sensibleres
Immich (Art 13) two_factor + group:admins Persönliche Fotos, oft auch Standortdaten
Paperless-ngx (Art 14) two_factor + group:admins Ausweise, Verträge, Rechnungen
File Browser two_factor + group:admins Direkter Zugriff aufs Filesystem – hochsensibel
Outline (Art 17) two_factor Auch für Nicht-Admins zugänglich (z. B. geteiltes Wiki)
Persönliche Bookmarks / RSS two_factor Standard für persönliche Apps

bypass vermeiden

Versuchung: „Mein Ghost-Blog soll sowieso öffentlich sein – setze ich auf bypass." Falsch herum gedacht. Wenn eine Domain in Authelia auf bypass steht, geht jeder Request unverändert durch – also auch /ghost/admin. Den Admin-Bereich schützt man besser gar nicht über Authelia, sondern wie in Artikel 07 angedeutet per Caddy-Regel auf VPN-only. bypass brauchst du nur für die Authelia-Subdomain selbst.

4. DNS-Vorbereitung

Wenn du Artikel 07 mit Wildcard-DNS abgeschlossen hast, ist hier nichts zu tun. Die Subdomain auth.deinedomain.de zeigt durch den *-A-Record bereits auf deinen VPS.

Quick-Check:

dig +short auth.deinedomain.de

Erwartung: Deine VPS-IPv4 erscheint.

Wenn nicht: Du hast Wildcard möglicherweise nicht gesetzt – zurück zu Artikel 07, Abschnitt 3.

5. Secrets generieren

Setup-Reihenfolge

Wir bereiten zuerst alles auf dem Server vor (Sektionen 5–8: Secrets, Verzeichnisse, Konfigurationsdateien), bevor der Stack in Komodo angelegt wird (Sektionen 9–13). Dadurch ist beim ersten Deploy bereits alles vorhanden, was Authelia zum Starten braucht.

Authelia braucht vier zufällige Geheimnisse:

  • JWT Secret – signiert die Tokens für den Identity-Validation-Flow (Passwort-Reset etc.)
  • Session Secret – signiert die Session-Cookies, die nach dem Login an alle deine Subdomains gehen
  • Storage Encryption Key – verschlüsselt die TOTP-Secrets und andere sensible Daten in der lokalen Datenbank
  • Redis Password – schützt Redis intern, auch wenn es nur im internen Netz erreichbar ist (eine Schutzschicht mehr)

Auf dem Server generieren:

echo "JWT Secret:        $(openssl rand -hex 32)"
echo "Session Secret:    $(openssl rand -hex 32)"
echo "Storage Enc Key:   $(openssl rand -hex 32)"
echo "Redis Password:    $(openssl rand -base64 24)"

Alle Werte in deinen Passwort-Manager. Sobald sie in der .env stehen, brauchst du sie nur noch im Disaster-Recovery-Fall.

⚠️ Der Storage Encryption Key ist nicht ersetzbar

JWT und Session Secret kannst du im Notfall neu generieren – User müssen sich danach nur neu einloggen. Der Storage Encryption Key dagegen verschlüsselt die TOTP-Secrets in der SQLite-Datenbank. Verlierst du ihn, sind die TOTP-Setups deiner User unbrauchbar – jeder muss seine 2FA-App neu koppeln. Backup dieses Werts ist Pflicht, nicht Empfehlung.

6. Verzeichnisse vorbereiten

Bevor wir die Datei schreiben, einmalig die komplette Verzeichnisstruktur vorbereiten. Komodo legt das Stack-Verzeichnis sonst erst beim Deploy an – aber dann sind wir zu spät dran, weil Authelia ohne Konfigurationsdatei nicht startet:

sudo mkdir -p /srv/docker/stacks/authelia/{config,data,log,redis-data}
sudo chown -R 1000:1000 /srv/docker/stacks/authelia/{data,log}

Hintergrund: Authelia läuft im Container als User 1000. Wenn das Host-Verzeichnis root gehört, scheitert SQLite beim Schreiben in ./data und der Log-Writer in ./log. Das redis-data-Verzeichnis braucht keinen chown, weil der Redis-Container intern als User redis (UID 999) läuft und sich seine Rechte selbst setzt.

7. configuration.yml

Das ist die zentrale Konfigurationsdatei von Authelia. Anders als compose.yaml und .env lässt sich diese Datei nicht sinnvoll über die Komodo-UI pflegen – wir legen sie direkt auf dem Server an.

Per VS Codium (falls du Remote-SSH in Artikel 07 eingerichtet hast):

Datei /srv/docker/stacks/authelia/config/configuration.yml neu anlegen und folgenden Inhalt einfügen.

Per SSH:

sudo nano /srv/docker/stacks/authelia/config/configuration.yml

In beiden Fällen ist der Inhalt:

###############################################################
# Authelia - Hauptkonfiguration
###############################################################

theme: 'dark'

# Server
server:
  address: 'tcp://0.0.0.0:9091/'
  endpoints:
    authz:
      forward-auth:
        implementation: 'ForwardAuth'

# Logs in Datei – vorbereitet für CrowdSec in Artikel 10
log:
  level: 'info'
  format: 'text'
  file_path: '/log/authelia.log'
  keep_stdout: true

# TOTP-Setup (Issuer = was in der Authenticator-App angezeigt wird)
totp:
  issuer: '{{ env "AUTHELIA_TOTP_ISSUER" }}'
  period: 30
  skew: 1

# WebAuthn (Passkeys / YubiKey – optional, aber empfehlenswert)
webauthn:
  display_name: 'Authelia'
  attestation_conveyance_preference: 'indirect'
  timeout: '60 seconds'

# Auth-Backend: lokale YAML-Datei (gut für 1–10 User; bei mehr → Authentik-Bonus)
authentication_backend:
  file:
    path: '/config/users_database.yml'
    watch: true
    password:
      algorithm: 'argon2'

# Access Control – one_factor als Sicherheitsnetz, sensible Apps mit Admin-Filter
access_control:
  default_policy: 'one_factor'
  rules:
    # Auth-Subdomain selbst: muss bypass sein, sonst kein Login möglich
    - domain: 'auth.{{ env "AUTHELIA_DOMAIN" }}'
      policy: 'bypass'

    # Sensible Apps: 2FA + Admin-Mitgliedschaft erforderlich
    - domain:
        - 'vault.{{ env "AUTHELIA_DOMAIN" }}'
        - 'immich.{{ env "AUTHELIA_DOMAIN" }}'
        - 'docs.{{ env "AUTHELIA_DOMAIN" }}'
        - 'files.{{ env "AUTHELIA_DOMAIN" }}'
      policy: 'two_factor'
      subject: ['group:admins']

    # Persönliche Apps mit 2FA, aber ohne Admin-Filter (z. B. geteiltes Wiki)
    - domain: 'wiki.{{ env "AUTHELIA_DOMAIN" }}'
      policy: 'two_factor'

# Session – Redis als Store + Cookie auf Parent-Domain für echtes SSO
session:
  secret: '{{ env "AUTHELIA_SESSION_SECRET" }}'
  redis:
    host: 'redis'
    port: 6379
    password: '{{ env "AUTHELIA_REDIS_PASSWORD" }}'
    database_index: 0
  cookies:
    - name: 'authelia_session'
      domain: '{{ env "AUTHELIA_DOMAIN" }}'
      authelia_url: '{{ env "AUTHELIA_URL" }}'
      default_redirection_url: 'https://{{ env "AUTHELIA_DOMAIN" }}'
      expiration: '12 hours'
      inactivity: '45 minutes'
      remember_me: '1 month'

# Rate-Limiting bei Login-Versuchen
regulation:
  max_retries: 3
  find_time: '2 minutes'
  ban_time: '10 minutes'

# Lokale SQLite-DB für User-Daten, TOTP-Secrets etc.
storage:
  encryption_key: '{{ env "AUTHELIA_STORAGE_ENCRYPTION_KEY" }}'
  local:
    path: '/data/db.sqlite3'

# Notifier – nutzt den Mail-Relay aus Artikel 08
notifier:
  disable_startup_check: false
  smtp:
    address: 'smtp://mail-relay:2500'
    sender: '{{ env "AUTHELIA_SMTP_SENDER" }}'
    disable_require_tls: true
    disable_starttls: true

# Identity Validation – JWT-Secret für Passwort-Reset-Links
identity_validation:
  reset_password:
    jwt_secret: '{{ env "AUTHELIA_JWT_SECRET" }}'

Was die wichtigsten Blöcke machen

Block Was passiert
server.endpoints.authz.forward-auth Aktiviert genau den Endpoint, den Caddys forward_auth gleich abfragt: /api/authz/forward-auth
log.file_path + keep_stdout Logs gehen zusätzlich in /log/authelia.log – CrowdSec liest die in Artikel 10. keep_stdout: true bedeutet: Komodo sieht weiterhin die Logs in der UI
authentication_backend.file Lokale YAML als User-DB. watch: true heißt: Änderungen werden live übernommen, ohne Container-Restart
access_control.default_policy: one_factor Sicherheitsnetz: unbekannte Subdomains bekommen mindestens Passwort-Schutz
subject: ['group:admins'] Zweiter Filter zusätzlich zu 2FA: der User muss auch in der admins-Gruppe sein
session.redis Sessions liegen in Redis statt im Container-Speicher → überleben Restarts
session.cookies[0].domain Parent-Domain. Cookie gilt für alle Subdomains → echtes SSO
regulation.max_retries: 3 Nach 3 Fehlversuchen ist der Account 10 Minuten gesperrt – einfache Brute-Force-Abwehr
notifier.smtp Authelia verschickt Mails über mail-relay:2500 – ohne TLS, ohne Auth, da das Docker-Netz lokal ist (siehe Artikel 08)
identity_validation.reset_password.jwt_secret Signiert die Token in Passwort-Reset-Links per Mail. Wichtig in 4.39+ – ältere Anleitungen zeigen oft noch eine Top-Level-jwt_secret-Variable, die ist abgekündigt

Warum SQLite und nicht PostgreSQL?

Die offiziellen Authelia-Docs raten beim File-Auth-Backend zu PostgreSQL und nennen SQLite „für Dev und Testing". Der Hintergrund ist Multi-Instance-Skalierung – mehrere Authelia-Container parallel hinter einem Load Balancer.

Im Self-Hosting für 1–3 User auf einem VPS ist das Szenario komplett irrelevant. SQLite läuft hier seit Jahren stabil, braucht null Setup und sichert sich mit dem ./data-Verzeichnis automatisch mit. Wer später echte Skalierung braucht, hat ohnehin schon den Sprung zu Authentik (Bonus-Artikel) gemacht.

Spätere Anpassungen

Diese Datei wirst du im Laufe der Serie noch häufiger anfassen – jedes Mal, wenn eine neue App dazukommt, ergänzt du in access_control.rules einen Eintrag mit der neuen Subdomain. Den gleichen Weg nutzen wie jetzt: VS Codium öffnen und Datei bearbeiten, oder per SSH sudo nano /srv/docker/stacks/authelia/config/configuration.yml. Dank watch: true übernimmt Authelia Änderungen sofort, ohne dass du den Stack redeployen musst.

8. Den ersten User anlegen

Authelia lädt User aus users_database.yml. Die Datei brauchen wir noch nicht beim ersten Start (Watch-Modus fängt sie später ab) – aber wir legen sie direkt mit an, damit du dich sofort einloggen kannst.

8.1 Passwort-Hash erzeugen

Authelia bringt einen Hash-Generator mit, den wir per Einmal-Container nutzen:

docker run --rm -it authelia/authelia:4.39 \
  authelia crypto hash generate argon2 \
  --password 'DEIN-STARKES-PASSWORT-HIER'

Erwartung: Eine Ausgabe wie

Digest: $argon2id$v=19$m=65536,t=3,p=4$Hjc8e7WYcBFcJmEDUOsS9A$ozM7RyZR1EyDR8cuyVpDDfmLrGPGFgo5E2NNqRumui4

Den kompletten String ab $argon2id$… kopieren – das ist der gehashte Wert, der gleich in die User-Datei kommt.

Wähle ein wirklich starkes Passwort

Hier ist keine Master-Hygiene-Lektion nötig: 20+ Zeichen aus deinem Passwort-Manager, einmal generieren, fertig. Dieses Passwort schützt nachher den Zugriff auf Vaultwarden, Immich, Paperless und alles Weitere gleichzeitig – ein gutes Passwort hier ist sehr viel wertvoller als drei mittelmäßige in den Apps selbst.

8.2 users_database.yml anlegen

Gleicher Weg wie bei der configuration.yml – per VS Codium oder per SSH:

sudo nano /srv/docker/stacks/authelia/config/users_database.yml

Inhalt:

---
users:
  martin:
    disabled: false
    displayname: 'Martin'
    password: '$argon2id$v=19$m=65536,t=3,p=4$Hjc8e7WYcBFcJmEDUOsS9A$ozM7RyZR1EyDR8cuyVpDDfmLrGPGFgo5E2NNqRumui4'
    email: 'martin@deine-domain.de'
    groups:
      - 'admins'

Das password-Feld bekommt den Hash aus Schritt 8.1 – nicht das Klartext-Passwort.

Wichtig: YAML ist whitespace-sensitiv. Nur Leerzeichen zum Einrücken, keine Tabs. Sonst quittiert Authelia beim Start mit:

could not parse the YAML database: yaml: line X: found character that cannot start any token

Die Gruppe admins ist genau die Gruppe, die in der configuration.yml per subject: ['group:admins'] als Filter genutzt wird. Spätere User für Family/Freunde lässt du einfach weg aus dieser Gruppe – sie kommen dann durch Login + 2FA bis zu den Subdomains ohne Admin-Filter (z. B. das geteilte Wiki), aber nicht auf Vaultwarden oder Paperless.

9. Stack in Komodo anlegen

Mit den Konfigurationsdateien auf dem Server geht es jetzt in Komodo weiter. Ab hier bis zum Deploy bleibt alles im Browser – kein Wechsel mehr.

In Komodo (http://vps:9120 oder über die Tailscale-IP):

  1. Links Stacks → oben rechts + New Stack
  2. Name: authelia
  3. Server: vps-homelab
  4. Stack Type: UI Defined
  5. Create

Im Stack-Editor pflegst du gleich compose.yaml (Sektion 10) und .env (Sektion 11). Beide Dateien greifen über env_file: .env und Volume-Mounts auf die in Sektion 6–8 vorbereiteten Verzeichnisse und Konfigurationsdateien zu.

10. compose.yaml

Im Compose-Editor:

name: authelia

services:
  authelia:
    image: authelia/authelia:latest
    container_name: authelia
    restart: unless-stopped
    env_file: .env
    volumes:
      - ./config:/config
      - ./data:/data
      - ./log:/log
    healthcheck:
      test: ["CMD", "authelia", "healthcheck"]
      interval: 30s
      timeout: 5s
      retries: 3
      start_period: 30s
    depends_on:
      redis:
        condition: service_healthy
    networks:
      - authelia_internal
      - homelab_net

  redis:
    image: redis:7-alpine
    container_name: authelia-redis
    restart: unless-stopped
    command: ["redis-server", "--requirepass", "${AUTHELIA_REDIS_PASSWORD}"]
    env_file: .env
    volumes:
      - ./redis-data:/data
    healthcheck:
      test: ["CMD", "redis-cli", "-a", "${AUTHELIA_REDIS_PASSWORD}", "ping"]
      interval: 10s
      timeout: 5s
      retries: 5
    networks:
      - authelia_internal

networks:
  authelia_internal:
  homelab_net:
    external: true

Was passiert hier?

Block Erklärung
image: authelia/authelia:4.39 Pinning auf Minor-Version. Patch-Updates innerhalb 4.39.x kommen automatisch, ein potentieller Sprung auf 4.40 wäre eine bewusste Entscheidung – analog postgres:17 und caddy-crowdsec:2
env_file: .env Sämtliche Secrets und Domain-Werte lädt Authelia aus der .env, nicht hardcoded ins Image
./config:/config Hier liegen configuration.yml und users_database.yml – die Konfigurations-Quelldateien
./data:/data SQLite-Datenbank und Notification-State. Das ist das Verzeichnis, das du regelmäßig sicherst
./log:/log Authelia-Logs als Datei – vorbereitet für CrowdSec-Acquisition in Artikel 10
authelia_internal Internes Netz für Authelia ↔ Redis. Redis ist von außen unerreichbar, auch nicht von anderen Apps im homelab_net
homelab_net Nur Authelia hängt mit drin, damit Caddy es als authelia:9091 erreichen kann
Redis requirepass Redis akzeptiert nur Verbindungen mit Passwort – auch im internen Netz
Kein ports: Beide Container sind ausschließlich intern erreichbar

Warum Redis?

Authelia kann grundsätzlich auch ohne Redis laufen – nutzt dann In-Memory-Session-Storage. Praktischer Effekt: Jedes Mal, wenn du den Authelia-Container neu startest (z. B. nach einem Update oder Stack-Redeploy), sind alle aktiven Sessions weg. Du wirst überall ausgeloggt und musst dich neu einloggen – Vaultwarden, Immich, Paperless, alles.

Bei monatlichen Updates ist das ein laufender, kleiner Ärger. Redis als Session-Store (ca. 10 MB Container) löst das: Sessions überleben Container-Restarts, „Remember me" funktioniert zuverlässig. Es ist auch die offizielle Empfehlung von Authelia für produktive Setups.

Pfade konsistent mit Artikel 07: alles unter /srv/docker/stacks/authelia/, von Komodo selbst angelegt.

11. .env

Im .env-Tab:

# Hauptdomain und Auth-Subdomain
AUTHELIA_DOMAIN=deine-domain.de
AUTHELIA_URL=https://auth.deine-domain.de

# TOTP-Issuer – erscheint später als App-Name in der Authenticator-App
AUTHELIA_TOTP_ISSUER=deinedomain.de

# Secrets aus Schritt 6
AUTHELIA_JWT_SECRET=<JWT-SECRET-AUS-OPENSSL>
AUTHELIA_SESSION_SECRET=<SESSION-SECRET-AUS-OPENSSL>
AUTHELIA_STORAGE_ENCRYPTION_KEY=<STORAGE-ENC-KEY-AUS-OPENSSL>
AUTHELIA_REDIS_PASSWORD=<REDIS-PASSWORD-AUS-OPENSSL>

# Mail-Versand: Sender-Adresse für Authelia-Mails
AUTHELIA_SMTP_SENDER="Authelia <noreply@deine-domain.de>"

# Zeitzone
TZ=Europe/Berlin

Authelia liest diese Werte über Template-Variablen in der configuration.yml (kommt im nächsten Schritt) – kein extra Setup nötig.

Auszutauschen sind:

  • deine-domain.de → deine tatsächliche Hauptdomain (vier Stellen: AUTHELIA_DOMAIN, AUTHELIA_URL, AUTHELIA_TOTP_ISSUER, AUTHELIA_SMTP_SENDER)
  • Die vier Secret-Platzhalter <...-AUS-OPENSSL> → die in Schritt 5 generierten Werte

Die Zeitzone passt für die meisten Self-Hoster, kann aber bei Bedarf angepasst werden.

12. Update-Verhalten konfigurieren

Bevor du deployst, zwei Schalter im Stack-Editor:

  • Poll for Updates: on
  • Auto Update: off

Standard für alle Stacks in dieser Serie – Komodo zeigt dir an, wenn ein neues Image verfügbar ist, deployt aber nicht automatisch.

13. Deploy und Logs prüfen

In Komodo im Stack-Overview: Deploy klicken.

Komodo schreibt alle Dateien auf den Host, zieht beide Images und startet die Container. In Komodo öffnen → Tab Containers:

  • authelia-redis sollte nach ~10 Sekunden Up (healthy) zeigen
  • authelia startet erst nach Redis (durch depends_on) und sollte nach ~30 Sekunden ebenfalls Up (healthy) zeigen

Erwartete Schlüsselzeilen in den Authelia-Logs:

level=info msg="Authelia v4.39.x is starting"
level=info msg="Startup checks completed successfully"
level=info msg="Listening for non-TLS connections on '0.0.0.0:9091'"

14. Caddy einbinden: die Auth-Subdomain und das (auth)-Snippet

Jetzt der entscheidende Schritt: Authelia ans Caddy hängen. Dafür ergänzt du im Caddyfile (Stack caddy, Tab Additional Files → Caddyfile) zwei Sachen.

14.1 Das (auth)-Snippet definieren

Direkt nach dem (public)-Snippet aus Artikel 07:

# ============================================================
# Snippet: Authelia Forward-Auth für persönliche Apps
# Wird per `import auth` in jeder geschützten Site verwendet
# ============================================================
(auth) {
    forward_auth authelia:9091 {
        uri /api/authz/forward-auth?authelia_url=https://auth.deine-domain.de/
        copy_headers Remote-User Remote-Groups Remote-Email Remote-Name
    }
}

Was passiert hier?

  • forward_auth authelia:9091 – Caddy fragt vor jedem Request bei Authelia an
  • uri /api/authz/forward-auth – genau der Endpoint, den wir in configuration.yml aktiviert haben (server.endpoints.authz.forward-auth)
  • ?authelia_url=https://auth.deine-domain.de/ – sagt Authelia, wohin der Browser für den Login-Flow umgeleitet wird
  • copy_headers Remote-User Remote-Groups Remote-Email Remote-Name – Wenn die Auth erfolgreich war, hängt Caddy diese Header an den Request, bevor er an die App geht. Apps wie Immich oder Paperless können das auswerten (z. B. um den User-Namen anzuzeigen)

14.2 Die Auth-Subdomain selbst

Direkt nach dem test-Block den Login-Portal-Block ergänzen:

# Authelia Login-Portal – muss öffentlich erreichbar sein
auth.deinedomain.de {
    import public
    reverse_proxy authelia:9091
}

Hier kein import auth – das wäre eine Endlosschleife, weil das Portal selbst nicht hinter sich selbst sitzen kann. Deshalb steht es in access_control auch auf bypass.

14.3 Caddy neu laden

Validieren und reloaden – Pattern aus Artikel 07:

docker exec caddy caddy validate --config /etc/caddy/Caddyfile
docker exec caddy caddy reload --config /etc/caddy/Caddyfile

Im Browser öffnen:

https://auth.deine-domain.de

Erwartung: Du siehst das Authelia-Login-Portal mit echtem Let's-Encrypt-Zertifikat. Schloss-Symbol klickbar → Aussteller Let's Encrypt.

15. Erster Login und 2FA einrichten

15.1 Login

Auf https://auth.deine-domain.de:

  • Username: martin (oder wie auch immer du den ersten User genannt hast)
  • Password: das Klartext-Passwort, das du in Schritt 10.1 zum Hashen genutzt hast

Du landest auf dem User-Portal. Hier kannst du jetzt 2FA einrichten – bevor du es brauchst.

15.2 TOTP einrichten

Im User-Portal auf Settings → Two-Factor Authentication → Set up One-Time Password.

Authelia schickt dir jetzt eine Bestätigungs-Mail an die Adresse aus users_database.yml – das ist der Moment, an dem dein Mail-Relay aus Artikel 08 erstmals produktiv zum Einsatz kommt.

Im Mail-Postfach erscheint eine Mail mit einem Bestätigungslink. Klick drauf → zurück im Browser zeigt Authelia einen QR-Code.

QR-Code mit deiner Authenticator-App scannen. Der Code erscheint unter dem Namen deine-domain.de – das ist der TOTP_ISSUER aus deiner .env.

Die App zeigt dir einen 6-stelligen Code, der sich alle 30 Sekunden ändert. Diesen Code im Setup-Formular eingeben → fertig.

Mail kommt nicht an?

Prüfe in dieser Reihenfolge:

  1. Mail-Relay-Container läuft? In Komodo: Container mail-relayUp.
  2. Logs des Mail-Relays in Komodo zeigen einen Versuch? Wenn nichts kommt, erreicht Authelia den Relay nicht – beide müssen im homelab_net sein.
  3. Mail im Spam-Ordner? Bei der ersten Mail von einer neuen Absender-Adresse passiert das oft.
  4. Bei wirklichem Versagen: notifier.filesystem als Alternative in configuration.yml – schreibt die Mails als Textdateien nach /data/notification.txt. Dann kannst du den Bestätigungslink dort rauskopieren. Aber das ist die Notlösung, nicht der Normalbetrieb.

15.3 Test: Login-Flow vollständig durchlaufen

Inkognito-Browser auf https://auth.deine-domain.de:

  1. Username + Passwort → Login klappt
  2. Bei einer geschützten App (z. B. später vault.deine-domain.de) wirst du zum 2FA-Schritt geleitet
  3. TOTP-Code aus der Authenticator-App eingeben
  4. → Weiterleitung zur App

15.4 Test: Session überlebt Container-Restart

Das ist der konkrete Mehrwert von Redis. Probier es einmal aus:

docker restart authelia

Anschließend im Browser eine geschützte Subdomain aufrufen – du bist nicht ausgeloggt. Ohne Redis wärst du an dieser Stelle wieder auf dem Login-Portal gelandet.

Optional: Passkeys statt 6-stelligem Code

Neben TOTP unterstützt Authelia auch WebAuthn – das ist die Technik hinter Passkeys (z. B. im Apple-/Google-Konto), Hardware-Sticks wie YubiKey, oder Touch ID auf dem Mac. Statt einen Code abzutippen, bestätigst du den Login per Fingerabdruck, Face ID oder USB-Stick. Vorteil: niemand kann dich auf einer gefälschten Login-Seite austricksen, weil der Browser den echten Server überprüft. Im User-Portal unter Set up WebAuthn einrichten.

Empfehlung: TOTP als Backup behalten (für den Fall, dass der Hardware-Stick mal nicht zur Hand ist), Passkeys/WebAuthn als bequeme Standard-Methode hinzufügen.

16. Wie das in den App-Artikeln aussehen wird

Damit du weißt, was du in Phase 3 erwartet: Jede geschützte App wird im Caddyfile in genau einem Block landen, mit zwei Imports:

# Vaultwarden (Artikel 12)
vault.deine-domain.de {
    import public
    import auth
    reverse_proxy vaultwarden:80
}

# Immich (Artikel 13)
immich.deine-domain.de {
    import public
    import auth
    reverse_proxy immich-server:2283
}

# Paperless-ngx (Artikel 14)
docs.deine-domain.de {
    import public
    import auth
    reverse_proxy paperless:8000
}

Drei Dinge passieren mit import auth:

  1. Vor jedem Request fragt Caddy bei Authelia an
  2. Bei ungültigem Cookie → Redirect auf das Login-Portal
  3. Bei gültigem Cookie + passender Policy → Request geht durch zur App, mit User-Headern

Plus: Die Domain muss in access_control.rules der configuration.yml stehen – sonst greift die default_policy: one_factor, was nur Passwort-Schutz bedeutet. Für sensible Apps trägst du die Subdomain mit two_factor + subject: ['group:admins'] ein, und Authelia liest die Datei dank watch: true automatisch neu.

17. Erweiterungs-Pfade

Weitere User anlegen

Pro neuem User: Passwort-Hash erzeugen (Schritt 10.1), Eintrag in users_database.yml ergänzen – dank watch: true wird der Account sofort gültig. Wenn der User keine Admin-Rechte bekommen soll: Gruppe admins weglassen. Er kommt dann durch Login + 2FA bis zu den Subdomains ohne Admin-Filter, aber nicht auf die sensiblen Apps.

Mehr Apps

Im access_control.rules-Block die Subdomain einfügen, Policy und ggf. subject festlegen. Authelia liest die Datei live nach – kein Stack-Restart nötig.

Wenn das nicht mehr reicht: Authentik (Bonus)

Drei Anzeichen, dass du über Authelia hinausgewachsen bist:

  • Du willst Apps wie Komodo oder Immich per OIDC-SSO anmelden (Authelia ist nur Forward-Auth-Proxy, kein OIDC-Provider für die App-internen Logins).
  • Du betreibst mehr als ~5 User und willst Rollen-/Gruppen-Verwaltung mit echter UI.
  • Du brauchst LDAP/SAML-Integration für andere Tools.

Dann lohnt sich der Wechsel zu Authentik. Der Bonus-Artikel im Anschluss an diese Serie zeigt, wie du Authelia in-place gegen Authentik tauschst, ohne den Rest der Infrastruktur anzufassen – Caddy bleibt, der (auth)-Snippet wird nur leicht umgeschrieben, der Rest läuft weiter wie zuvor.

Für 95 % der Self-Hosting-Setups ist Authelia ausreichend und bleibt es auch.

Und jetzt?

Phase 2 ist damit für die Pflicht-Bausteine durch. Du hast eine Auth-Schicht mit 2FA, die in Phase 3 vor jede persönliche App gestellt wird – mit einer einzigen Zeile (import auth) pro Caddy-Block.

Wer den optionalen Sicherheitsblock noch dazunehmen will, macht jetzt Artikel 10 (CrowdSec) weiter. CrowdSec ergänzt Authelia gut: Authelia schützt das Login-Formular vor Brute Force auf Account-Ebene (über regulation), CrowdSec auf IP-Ebene über alle Apps hinweg. Die Authelia-Logs liegen dank ./log:/log schon im richtigen Format vor – CrowdSec liest sie direkt mit. Das CrowdSec-Plugin steckt seit Artikel 07 schon im Caddy-Image, nur eingeschaltet wird es noch nicht.

Wer das überspringen will, geht direkt zu Artikel 11 (App-Baukasten) – dem Bindeglied zwischen Infrastruktur und den ersten echten Apps in Phase 3.

👉 Nächster Artikel: 10. CrowdSec – Angriffserkennung für öffentliche Web-Dienste (optional)
👉 Oder direkt: 11. App-Baukasten verstehen und nutzen