Pocket ID installieren: Single Sign-On mit Passkeys für deine Self-Hosted-Apps
Pocket ID mit Docker und Caddy installieren und Apps wie Paperless-ngx, Actual Budget und Forgejo anbinden. Mit Passkeys, Gruppen und Beispielen.

Inhalt
Pocket ID 2.19.2Caddy 2.11Docker 29.9Ubuntu 26.04Actual Budget 26.11.0Paperless-ngx 3.4.2Forgejo 17.0.1nginx 1.30oauth2-proxy 7.15Wer mehrere selbst gehostete Apps betreibt, kennt das Problem: Jede App hat ihr eigenes Login mit eigenem Passwort. Pocket ID löst das. Du meldest dich einmal bei Pocket ID an, und zwar mit einem Passkey statt mit einem Passwort. Danach loggst du dich bei allen verbundenen Apps mit einem Klick ein.
In diesem Beitrag installiere ich Pocket ID mit Docker und Caddy, lege mein Konto an und verbinde vier Beispiele: Actual Budget, Paperless-ngx, Forgejo und eine normale Webseite, die gar keine eigene Anmeldung kennt.
Was ist Pocket ID?
Pocket ID ist ein OpenID-Connect-Anbieter (OIDC). Das ist ein Standard, mit dem eine App die Anmeldung an einen anderen Dienst auslagert. Die App fragt Pocket ID: “Wer ist das?”, und Pocket ID antwortet mit Benutzername und E-Mail-Adresse.
Das Besondere an Pocket ID: Es kennt keine Passwörter. Du meldest dich ausschließlich mit Passkeys an, zum Beispiel mit Fingerabdruck oder Gesichtserkennung am Handy, einem Passwortmanager oder einem Hardware-Schlüssel. Pocket ID ist deutlich schlanker als Authentik oder Keycloak und braucht nur einen einzigen Container.
Das sind die wichtigsten Begriffe:
| Begriff | Bedeutung |
|---|---|
| OIDC | OpenID Connect, der Standard für die Anmeldung über einen zentralen Dienst. |
| Client | Eine App, die sich bei Pocket ID anmeldet, zum Beispiel Paperless-ngx. |
| Passkey | Anmeldung ohne Passwort, die auf einem kryptografischen Schlüssel auf deinem Gerät beruht. |
| Callback-URL | Die Adresse, zu der Pocket ID den Benutzer nach der Anmeldung zurückschickt. |
| Client-ID und Client-Secret | Zugangsdaten, mit denen sich die App bei Pocket ID ausweist. |
| Benutzergruppe | Sammlung von Benutzern, mit der du den Zugriff auf Apps steuerst. |
Voraussetzungen
- Einen Server: Ich nutze einen kleinen VPS bei Hetzner mit Ubuntu 26.04. Pocket ID selbst braucht kaum Ressourcen.
- Eine Domain mit Subdomains: Pocket ID bekommt eine eigene Adresse, zum Beispiel
{{id_domain}}, und jede App ebenfalls, zum Beispielpaperless.{{base_domain}}. Alle müssen per A-Record auf deinen Server zeigen. Ein Wildcard-Eintrag*.{{base_domain}}spart dir die einzelnen Einträge. - Offene Ports: 80 und 443 müssen erreichbar sein.
- Ein Gerät mit Passkey-Unterstützung: Das kann dein Smartphone, dein Passwortmanager oder ein Sicherheitsschlüssel sein. Passkeys funktionieren nur über HTTPS, deshalb brauchst du die Domain.
Docker installieren
Verbinde dich per SSH mit dem Server und installiere Docker:
curl -fsSL https://get.docker.com | sudo shDanach legst du ein gemeinsames Docker-Netzwerk an. Über dieses Netzwerk erreicht Caddy später alle Apps:
sudo docker network create proxyPocket ID mit Docker Compose und Caddy starten
Erstelle ein Verzeichnis für Pocket ID:
mkdir ~/pocketid
cd ~/pocketidLege die compose.yaml an:
nano compose.yamlservices:
pocket-id:
image: pocketid/pocket-id:v2
container_name: pocket-id
restart: unless-stopped
env_file: .env
volumes:
- ./data:/app/data
healthcheck:
test: ["CMD", "/app/pocket-id", "healthcheck"]
interval: 1m30s
timeout: 5s
retries: 2
start_period: 10s
networks:
- proxy
caddy:
image: caddy:2
container_name: caddy
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile
- caddy_data:/data
- caddy_config:/config
networks:
- proxy
volumes:
caddy_data:
caddy_config:
networks:
proxy:
external: trueDer Ordner ./data enthält die Datenbank von Pocket ID. Der Tag v2 hält dich auf der aktuellen Hauptversion.
Erzeuge einen zufälligen Schlüssel:
openssl rand -base64 32Lege die .env-Datei an und trage dort den Schlüssel ein:
nano .envAPP_URL=https://{{id_domain}}
ENCRYPTION_KEY=HIER-DEN-ZUFAELLIGEN-SCHLUESSEL-EINTRAGEN
TRUST_PROXY=true
PUID=1000
PGID=1000APP_URList die Adresse, unter der du Pocket ID öffnest, ohne Pfad am Ende.ENCRYPTION_KEYverschlüsselt sensible Daten wie die Signaturschlüssel. Sichere ihn, denn ohne ihn lässt sich die Datenbank nicht mehr nutzen.TRUST_PROXY=truesorgt dafür, dass Pocket ID im Audit-Protokoll die echte IP-Adresse der Benutzer sieht und nicht die von Caddy.PUIDundPGIDlegen fest, welcher Benutzer die Dateien in./databesitzt.
Lege die Caddyfile an:
nano Caddyfile{{id_domain}} {
reverse_proxy pocket-id:1411
}Starte beide Container:
sudo docker compose up -dMit sudo docker compose ps prüfst du den Status. Pocket ID meldet healthy, sobald es bereit ist. Ob Caddy das Zertifikat bekommen hat, siehst du hier:
sudo docker compose logs caddy | grep "certificate obtained"Dein Admin-Konto anlegen
Öffne https://{{id_domain}}/setup im Browser. Diese Seite funktioniert nur, solange es noch kein Konto gibt. Dort legst du Benutzername, E-Mail-Adresse und Namen fest und klickst auf “Sign Up”. Danach fügst du mit “Add Passkey” deinen ersten Passkey hinzu.
Erledige das sofort nach dem Start. Solange kein Konto existiert, kann jeder, der die Adresse kennt, das Admin-Konto anlegen. Auf meinem Testserver gab es schon wenige Minuten nach dem Start Zugriffe von Scannern auf die Domain.
Du landest danach in den Einstellungen. Unter “Mein Konto” siehst du deine Passkeys:

Pocket ID weist oben darauf hin, dass nur ein Passkey hinterlegt ist. Füge mit “Passkey hinzufügen” einen zweiten hinzu, zum Beispiel auf einem anderen Gerät. Sonst sperrst du dich aus, wenn du das einzige Gerät verlierst. Die Sprache stellst du am Ende derselben Seite um.
Die Oberfläche im Überblick
Links findest du “Mein Konto”, “Meine Apps” und “Audit-Protokoll”. Unter “Verwaltung” liegen die Administrationsseiten für Benutzer, Benutzergruppen und OIDC-Clients.
Unter “Benutzer” siehst du alle Konten. Der Benutzer “Static API User” erscheint nur, wenn du in der .env einen STATIC_API_KEY gesetzt hast, und lässt sich ignorieren:

Eine App verbinden: So funktioniert es immer
Jede App läuft nach demselben Muster an. Du legst in Pocket ID einen Client an, kopierst drei Werte in die App und gibst die Callback-URL der App in Pocket ID ein.
Gehe zu “Verwaltung” -> “OIDC Clients” und klicke auf “OIDC-Client hinzufügen”. Es erscheint ein Formular:

Du füllst drei Dinge aus:
- Name: Der Name der App. Er erscheint später beim Anmelden.
- Clienttyp: “Vertraulicher Client” ist richtig für Apps mit eigenem Server, das sind fast alle in diesem Beitrag. “Öffentlicher Client” ist für reine Browser-, Mobil- oder Desktop-Apps gedacht.
- Callback-URL: Die Adresse, zu der Pocket ID den Benutzer zurückschickt. Pocket ID leitet nur an URLs weiter, die du hier einträgst. Die richtige URL steht in der Dokumentation der jeweiligen App, unten findest du sie für alle Beispiele.
Nach “Erzeugen” öffnet sich die Seite des Clients. Dort stehen alle Werte, die du für die App brauchst:

Über “Mehr Details anzeigen” siehst du zusätzlich die einzelnen Adressen:

Die meisten Apps brauchen nur die Client-ID, das Client-Secret und die Discovery-URL https://{{id_domain}}/.well-known/openid-configuration. Manche Apps wollen die Adressen einzeln haben:
| Wert | Adresse |
|---|---|
| Aussteller (Issuer) | https://{{id_domain}} |
| Autorisierung | https://{{id_domain}}/authorize |
| Token | https://{{id_domain}}/api/oidc/token |
| Benutzerinfo | https://{{id_domain}}/api/oidc/userinfo |
| Abmelden | https://{{id_domain}}/api/oidc/end-session |
Das Client-Secret zeigt Pocket ID nur ein einziges Mal direkt nach dem Erstellen. Kopiere es sofort. Unter dem Reiter “Zugangsdaten” legst du bei Bedarf ein neues Secret an, und mehrere Secrets können gleichzeitig aktiv sein:

Als Scopes nutzen die meisten Apps openid profile email.
Beispiel 1: Actual Budget
Actual Budget hat OIDC eingebaut. Die Callback-URL lautet https://actual.{{base_domain}}/openid/callback. Lege in Pocket ID einen Client mit diesem Namen und dieser URL an und kopiere Client-ID und Secret.
Wie du Actual Budget selbst installierst, steht in meiner Anleitung zu Actual Budget. Für Pocket ID legst du neben der compose.yaml eine .env an:
ACTUAL_OPENID_DISCOVERY_URL=https://{{id_domain}}
ACTUAL_OPENID_CLIENT_ID=DEINE-CLIENT-ID
ACTUAL_OPENID_CLIENT_SECRET=DEIN-CLIENT-SECRET
ACTUAL_OPENID_SERVER_HOSTNAME=https://actual.{{base_domain}}ACTUAL_OPENID_SERVER_HOSTNAME ist die Adresse von Actual selbst. Ohne diese Zeile bietet Actual die OpenID-Anmeldung nicht an. In der Docker-Compose-Datei von Actual muss der Service die Datei einlesen:
services:
actual_server:
image: actualbudget/actual-server:latest
container_name: actual_server
restart: unless-stopped
env_file: .env
volumes:
- ./data:/data
networks:
- proxy
networks:
proxy:
external: trueWichtig ist env_file: Docker Compose liest eine .env-Datei nur für Platzhalter in der Compose-Datei, nicht automatisch in den Container. Starte Actual neu mit sudo docker compose up -d --force-recreate.
Ergänze in der Caddyfile von Pocket ID einen Eintrag für die App und lade Caddy neu:
actual.{{base_domain}} {
reverse_proxy actual_server:5006
}cd ~/pocketid
sudo docker compose exec caddy caddy reload --config /etc/caddy/CaddyfileDieses Muster gilt für alle Beispiele. Ein neuer Eintrag in der Caddyfile, ein Reload, fertig. Der Name hinter reverse_proxy ist der container_name der App, und die App muss im Netzwerk proxy hängen.
Wenn du https://actual.{{base_domain}} öffnest, siehst du jetzt “Bei dieser Actual-Instanz anmelden” mit dem Button “OpenID verwenden”:

Der erste Benutzer, der sich über OpenID anmeldet, wird Servereigentümer. Das lässt sich nicht über die Oberfläche ändern. Melde dich also zuerst selbst an. Nach dem Klick fragt Pocket ID, ob Actual auf deine E-Mail-Adresse und dein Profil zugreifen darf. Du bestätigst mit “Anmelden”. Oben rechts in Actual steht danach “angemeldet als” mit deinem Benutzernamen:

Beispiel 2: Paperless-ngx
Für Paperless-ngx lautet die Callback-URL:
https://paperless.{{base_domain}}/accounts/oidc/pocket-id/login/callback/Das Stück pocket-id in der Mitte ist die provider_id aus der Konfiguration unten. Beides muss übereinstimmen. Die Installation von Paperless-ngx beschreibe ich in meiner Paperless-Anleitung für den VPS. Auf dem Webserver-Container setzt du zwei Umgebungsvariablen. Ich lege sie in eine .env-Datei, die der Service per env_file einliest:
PAPERLESS_URL=https://paperless.{{base_domain}}
PAPERLESS_APPS=allauth.socialaccount.providers.openid_connect
PAPERLESS_SOCIALACCOUNT_PROVIDERS={"openid_connect":{"SCOPE":["openid","profile","email"],"OAUTH_PKCE_ENABLED":true,"APPS":[{"provider_id":"pocket-id","name":"Pocket ID","client_id":"DEINE-CLIENT-ID","secret":"DEIN-CLIENT-SECRET","settings":{"server_url":"https://{{id_domain}}"}}]}}PAPERLESS_SOCIALACCOUNT_PROVIDERS muss in einer einzigen Zeile stehen. Ohne Anführungszeichen drumherum, sonst liest Compose sie mit.
Nach einem Neustart zeigt die Anmeldeseite von Paperless unter dem Passwortformular den Eintrag “oder anmelden über” und einen Button “Pocket ID”. Beim ersten Login fragt Paperless noch einmal nach Benutzername und E-Mail-Adresse, vorausgefüllt aus Pocket ID:

Mit der Variable PAPERLESS_SOCIAL_AUTO_SIGNUP=true überspringst du diese Seite, und Paperless legt den Benutzer sofort an.
Wichtig: Ein neuer Benutzer über Pocket ID hat in Paperless zunächst keine Rechte. Die Oberfläche lädt dann nicht richtig, und im Log stehen Zeilen wie Forbidden: /api/ui_settings/. Du hast zwei Wege:
- Einmalig: Ein bestehender Admin gibt dem Benutzer unter “Einstellungen” -> “Benutzer & Gruppen” Rechte. Hast du noch keinen Admin, geht es auf dem Server:
sudo docker compose exec webserver python3 manage.py shell -c "
from django.contrib.auth.models import User
u = User.objects.get(username='dein-benutzername')
u.is_superuser = True
u.is_staff = True
u.save()
"- Dauerhaft: Mit
PAPERLESS_SOCIAL_ACCOUNT_DEFAULT_GROUPSweist du neuen SSO-Benutzern automatisch eine Paperless-Gruppe zu, deren Rechte du vorher in Paperless angelegt hast.
Danach öffnet sich das Dashboard:

Zwei weitere Variablen sind praktisch: PAPERLESS_REDIRECT_LOGIN_TO_SSO=true leitet die Anmeldeseite direkt zu Pocket ID weiter. Mit PAPERLESS_DISABLE_REGULAR_LOGIN=true verschwindet das Passwortformular. Aktiviere Letzteres erst, wenn der SSO-Login sicher funktioniert und mindestens ein Konto Adminrechte hat.
Beispiel 3: Forgejo
Für Forgejo lautet die Callback-URL:
https://git.{{base_domain}}/user/oauth2/PocketID/callbackForgejo baut die Callback-URL aus dem Namen der Anmeldequelle. Der Teil PocketID in der URL muss deshalb genau dem Namen entsprechen, den du weiter unten bei “Authentifizierungsname” einträgst, auch in der Groß- und Kleinschreibung. Wählst du einen anderen Namen, ersetzt du PocketID in der URL durch diesen Namen. Wie du Forgejo installierst, steht in meiner Forgejo-Anleitung.
Es gibt zwei Wege. Über die Oberfläche gehst du in Forgejo zu “Administratoreinstellungen” -> “Identität u. Zugriff” -> “Authentifizierungsquellen” und klickst auf “Authentifizierungsquelle hinzufügen”. Dort wählst du:
- Authentifizierungstyp: OAuth2
- Authentifizierungsname:
PocketID - OAuth2-Anbieter: OpenID Connect
- Client-ID und Client-Geheimnis: die Werte aus Pocket ID
- OpenID-Connect-Auto-Discovery-URL:
https://{{id_domain}}/.well-known/openid-configuration - Zusätzliche Bereiche:
openid email profile - Lokale 2FA überspringen: angehakt, denn Pocket ID übernimmt die Absicherung
Schneller geht es auf dem Server mit einem einzigen Befehl:
sudo docker exec -u git forgejo forgejo admin auth add-oauth \
--name PocketID \
--provider openidConnect \
--key "DEINE-CLIENT-ID" \
--secret "DEIN-CLIENT-SECRET" \
--auto-discover-url https://{{id_domain}}/.well-known/openid-configuration \
--scopes "openid email profile" \
--skip-local-2faMit sudo docker exec -u git forgejo forgejo admin auth list prüfst du das Ergebnis. In der Oberfläche steht die Quelle dann in der Liste:

Auf der Anmeldeseite von Forgejo erscheint der Button “Anmelden mit PocketID”:

Beim ersten Login fragt Forgejo, ob du ein neues Konto anlegen oder ein bestehendes verknüpfen möchtest. Danach bist du angemeldet:

Beispiel 4: Eine Seite ohne eigenes Login schützen
Nicht jede App kann OIDC. Eine statische Webseite, ein Dashboard oder ein Tool ohne Login lassen sich trotzdem hinter Pocket ID setzen. Dafür brauchst du oauth2-proxy. Das ist ein kleiner Dienst, der die Anmeldung bei Pocket ID übernimmt. Dein Reverse Proxy fragt ihn bei jedem Aufruf, ob der Besucher angemeldet ist.
Der Ablauf: Ein Besucher ruft https://site.{{base_domain}} auf. Der Reverse Proxy fragt oauth2-proxy. Ist der Besucher nicht angemeldet, schickt er ihn zu Pocket ID. Nach der Anmeldung liefert er die Seite aus. Die App dahinter braucht von alldem nichts zu wissen.
Das geht mit Caddy, den du ohnehin für HTTPS nutzt, und mit nginx. Ich zeige beide Varianten. Nimm eine davon, denn beide brauchen dieselbe Adresse site.{{base_domain}}.
Lege in Pocket ID einen Client an, zum Beispiel mit dem Namen “Caddy Seite” und der Callback-URL:
https://site.{{base_domain}}/oauth2/callbackBeide Varianten brauchen dieselbe .env für oauth2-proxy. Lege sie im Ordner der Variante an, die du nutzt. Das Cookie-Secret erzeugst du mit openssl rand -base64 32 | tr '+/' '-_':
OAUTH2_PROXY_PROVIDER=oidc
OAUTH2_PROXY_OIDC_ISSUER_URL=https://{{id_domain}}
OAUTH2_PROXY_CLIENT_ID=DEINE-CLIENT-ID
OAUTH2_PROXY_CLIENT_SECRET=DEIN-CLIENT-SECRET
OAUTH2_PROXY_COOKIE_SECRET=DEIN-COOKIE-SECRET
OAUTH2_PROXY_REDIRECT_URL=https://site.{{base_domain}}/oauth2/callback
OAUTH2_PROXY_EMAIL_DOMAINS=*
OAUTH2_PROXY_HTTP_ADDRESS=0.0.0.0:4180
OAUTH2_PROXY_COOKIE_SECURE=true
OAUTH2_PROXY_REVERSE_PROXY=true
OAUTH2_PROXY_SET_XAUTHREQUEST=true
OAUTH2_PROXY_CODE_CHALLENGE_METHOD=S256
OAUTH2_PROXY_INSECURE_OIDC_ALLOW_UNVERIFIED_EMAIL=true
OAUTH2_PROXY_SKIP_PROVIDER_BUTTON=trueEin paar Zeilen erkläre ich:
OAUTH2_PROXY_EMAIL_DOMAINS=*erlaubt jede E-Mail-Domain. Wer sich anmelden darf, regelst du in Pocket ID über Gruppen.OAUTH2_PROXY_INSECURE_OIDC_ALLOW_UNVERIFIED_EMAIL=truebrauchst du, weil Pocket ID E-Mail-Adressen standardmäßig nicht als verifiziert markiert,oauth2-proxyverifizierte Adressen aber standardmäßig erwartet.OAUTH2_PROXY_SKIP_PROVIDER_BUTTON=trueschickt Besucher direkt zu Pocket ID, statt erst eine Seite mit einem Button anzuzeigen.OAUTH2_PROXY_CODE_CHALLENGE_METHOD=S256schaltet PKCE ein, einen zusätzlichen Schutz gegen abgefangene Anmeldecodes.
Variante A: Caddy mit forward_auth
Caddy hat dafür die Anweisung forward_auth. Du brauchst keinen zusätzlichen Webserver und keine zusätzliche Konfigurationsdatei: Eine App schützt du mit einem Block in der Caddyfile.
Lege einen Ordner für oauth2-proxy an und speichere die .env von oben darin:
mkdir ~/oauth2-proxy
cd ~/oauth2-proxy
nano .envDie compose.yaml enthält nur oauth2-proxy:
services:
oauth2-proxy:
image: quay.io/oauth2-proxy/oauth2-proxy:latest
container_name: oauth2-proxy
restart: unless-stopped
env_file: .env
networks:
- proxy
networks:
proxy:
external: trueStarte es mit sudo docker compose up -d. Dann ersetzt du in der Caddyfile von Pocket ID den Eintrag für site.{{base_domain}} durch diesen Block:
site.{{base_domain}} {
route {
reverse_proxy /oauth2/* oauth2-proxy:4180 {
header_up X-Real-IP {remote_host}
header_up X-Forwarded-Uri {uri}
}
forward_auth oauth2-proxy:4180 {
uri /oauth2/auth
header_up X-Real-IP {remote_host}
copy_headers X-Auth-Request-User X-Auth-Request-Email X-Auth-Request-Preferred-Username
@error status 401
handle_response @error {
redir * /oauth2/sign_in?rd={scheme}://{host}{uri}
}
}
reverse_proxy DEINE-APP:80
}
}So funktioniert der Block:
routelegt fest, dass Caddy die Anweisungen in dieser Reihenfolge abarbeitet. Ohneroutesortiert Caddy sie selbst, und die Prüfung kann nach der App laufen.- Die erste Zeile gibt alle Adressen unter
/oauth2/anoauth2-proxyweiter, zum Beispiel die Anmeldeseite und den Callback. forward_authfragtoauth2-proxybei jedem Aufruf, ob der Besucher angemeldet ist. Bei einem401leitet dieredir-Zeile zur Anmeldung weiter. Sonst kopiertcopy_headersBenutzername und E-Mail-Adresse als Header an die App.reverse_proxy DEINE-APP:80ist deine eigentliche App. Der Name ist dercontainer_name, und der Container hängt im Netzwerkproxy.
Lade Caddy neu:
cd ~/pocketid
sudo docker compose exec caddy caddy reload --config /etc/caddy/CaddyfileIch habe den Block mit einer Testseite geprüft. Statt reverse_proxy stand dort diese Zeile, die den Header aus der Anmeldung ausgibt:
respond "Angemeldet als {http.request.header.X-Auth-Request-Preferred-Username} ({http.request.header.X-Auth-Request-Email})"Wenn du https://site.{{base_domain}} öffnest und bei Pocket ID noch nicht angemeldet bist, siehst du den Anmeldebildschirm. Dort klickst du auf “Anmelden” und bestätigst mit deinem Passkey. Bist du schon angemeldet, fragt Pocket ID nur noch nach der Freigabe:

Danach liefert Caddy die Seite aus:

Deine App bekommt die Header X-Auth-Request-Preferred-Username und X-Auth-Request-Email bei jedem Aufruf mit. Apps, die einen vorgelagerten Login unterstützen, können damit Benutzer automatisch anlegen oder erkennen. Das heißt “Header Authentication” und steht in der Doku der jeweiligen App.
Beim Abmelden gibt es einen Stolperstein. Der Link /oauth2/sign_out beendet nur die Sitzung bei oauth2-proxy. Läuft die Sitzung bei Pocket ID noch, leitet Pocket ID dich beim nächsten Aufruf ohne Rückfrage wieder zurück, und du bist sofort wieder angemeldet. Willst du dich ganz abmelden, meldest du dich zusätzlich in Pocket ID ab. Dort klickst du oben rechts auf deinen Avatar und dann auf “Abmelden”.
Variante B: nginx mit auth_request
Läuft deine Seite schon hinter nginx, setzt du die Prüfung dort ein. Hier ist es eine statische Seite. Alles passiert in einem Ordner:
Erstelle dann ein Verzeichnis mit drei Dateien:
mkdir -p ~/nginx-site/html
cd ~/nginx-siteDie Datei html/index.html ist deine Seite. Für das Beispiel reicht eine einfache:
<!doctype html>
<html lang="de">
<head><meta charset="utf-8"><title>Geschützte Seite</title></head>
<body>
<h1>Geschützte Seite</h1>
<p>Diese Seite liefert nginx aus. Ohne Anmeldung bei Pocket ID siehst du sie nicht.</p>
<p><a href="/whoami">Wer bin ich?</a> · <a href="/oauth2/sign_out">Abmelden</a></p>
</body>
</html>Die Datei html/whoami.html zeigt nach dem Login, wer angemeldet ist. nginx füllt die Platzhalter per SSI (Server Side Includes):
<p>Angemeldet als <!--# echo var="user" --> (<!--# echo var="email" -->)</p>Die Datei nginx.conf ist der Kern:
server {
listen 80;
server_name site.{{base_domain}};
location /oauth2/ {
proxy_pass http://oauth2-proxy:4180;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Auth-Request-Redirect $request_uri;
}
location = /oauth2/auth {
internal;
proxy_pass http://oauth2-proxy:4180;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header Content-Length "";
proxy_pass_request_body off;
}
location / {
auth_request /oauth2/auth;
error_page 401 = /oauth2/sign_in;
auth_request_set $user $upstream_http_x_auth_request_preferred_username;
auth_request_set $email $upstream_http_x_auth_request_email;
root /usr/share/nginx/html;
index index.html;
}
location = /whoami {
auth_request /oauth2/auth;
error_page 401 = /oauth2/sign_in;
auth_request_set $user $upstream_http_x_auth_request_preferred_username;
auth_request_set $email $upstream_http_x_auth_request_email;
ssi on;
default_type text/html;
alias /usr/share/nginx/html/whoami.html;
}
}Die Zeile auth_request /oauth2/auth; ist der Türsteher. Sie steht in jedem location-Block, den du schützen willst. Alles, was in location / steht, ist nur durch den Login erreichbar.
Die .env von oben legst du in denselben Ordner. Die compose.yaml enthält nginx und oauth2-proxy:
services:
nginx:
image: nginx:stable
container_name: nginx_site
restart: unless-stopped
volumes:
- ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
- ./html:/usr/share/nginx/html:ro
depends_on:
- oauth2-proxy
networks:
- proxy
oauth2-proxy:
image: quay.io/oauth2-proxy/oauth2-proxy:latest
container_name: oauth2-proxy
restart: unless-stopped
env_file: .env
networks:
- proxy
networks:
proxy:
external: trueAuch hier ein Eintrag in der Caddyfile von Pocket ID und ein Reload. Der alte Block für site.{{base_domain}} aus Variante A entfällt:
site.{{base_domain}} {
reverse_proxy nginx_site:80
}Starte alles mit sudo docker compose up -d. Beim ersten Aufruf von https://site.{{base_domain}} leitet dich nginx zu Pocket ID weiter, das fragt die Freigabe ab:

Nach der Bestätigung siehst du deine Seite:

Unter /whoami zeigt nginx, wer angemeldet ist. Die Werte kommen aus den Headern, die oauth2-proxy mitliefert. Du kannst sie in jeder App hinter nginx weiterreichen, zum Beispiel mit proxy_set_header X-User $user;:

Wenn du nginx-Konfigurationsdateien änderst, die du als einzelne Datei in den Container mountest, lade nicht nur neu. Viele Editoren und sed -i ersetzen die Datei durch eine neue, und der Container sieht dann weiter die alte. Starte den Container nach solchen Änderungen mit sudo docker compose up -d --force-recreate nginx neu.
Weitere Apps
Für über 100 Apps gibt es fertige Anleitungen in der Pocket-ID-Dokumentation, darunter Immich, Karakeep, Nextcloud, Vaultwarden, Proxmox, Jellyfin, Open WebUI, Gitea, Grafana und Portainer. Das Muster ist immer gleich: Client anlegen, Callback-URL eintragen, Client-ID, Secret und Discovery-URL in die App kopieren. Wie Karakeep läuft, steht in meiner Karakeep-Anleitung.
Zugriff mit Benutzergruppen einschränken
Standardmäßig darf jeder Benutzer von Pocket ID sich bei jedem Client anmelden. Das änderst du mit Gruppen. Das ist sinnvoll, wenn du Familie, Kollegen oder Freunde auf deinem Server hast, aber nicht jeder jede App benutzen soll.
Gehe zu “Verwaltung” -> “Benutzergruppen” und klicke auf “Gruppe hinzufügen”. Du gibst einen Anzeigenamen und einen Namen ein. Der Name erscheint später im groups-Claim, den Apps wie Grafana oder Paperless für Rollen auswerten können. Pocket ID füllt das Namensfeld automatisch aus dem Anzeigenamen. Prüfe es, bevor du speicherst, sonst steht der Text doppelt darin.

Im Reiter “Benutzer” der Gruppe wählst du die Mitglieder aus und speicherst:

Danach liegt die Gruppe in der Übersicht:

Jetzt schränkst du einen Client ein. Öffne ihn unter “OIDC Clients”, wechsle in den Reiter “Zugang” und wähle “Ausgewählte Benutzergruppen”. Dann hakst du die erlaubten Gruppen an und klickst auf “Speichern”:

Benutzer, die in keiner dieser Gruppen sind, sehen bei der Anmeldung diese Meldung:

Im Reiter “Zugang” kannst du jederzeit wieder auf “Alle Benutzer” zurückstellen. Wichtig: Wählst du “Ausgewählte Benutzergruppen” und hakst keine Gruppe an, darf niemand mehr den Client nutzen.
Meine Apps
Unter “Meine Apps” sieht jeder Benutzer die Apps, für die er freigeschaltet ist, und öffnet sie mit einem Klick. Damit eine App dort einen Link bekommt, trägst du im Client unter “Allgemein” die “Client-Start-URL” ein, zum Beispiel https://paperless.{{base_domain}}:

So wird Pocket ID zum Startpunkt für deinen ganzen Server: einmal anmelden, dann alle Apps von einer Seite aus öffnen.
Weitere Einstellungen
Die wichtigsten Umgebungsvariablen für die .env von Pocket ID, wie sie in der Dokumentation stehen:
| Variable | Wirkung |
|---|---|
ANALYTICS_DISABLED=true |
Schaltet die tägliche Zählung laufender Instanzen ab. |
VERSION_CHECK_DISABLED=true |
Schaltet die Suche nach neuen Versionen auf GitHub ab. |
STATIC_API_KEY |
Ein API-Schlüssel mit Adminrechten (mindestens 16 Zeichen), etwa für Skripte. Er wird im Header X-API-KEY mitgeschickt. |
MAXMIND_LICENSE_KEY |
Lädt eine Geodatenbank für die Standortanzeige im Audit-Protokoll. |
UI_CONFIG_DISABLED=true |
Nimmt die Einstellungen aus der Oberfläche aus Umgebungsvariablen. Nur dann wirken die SMTP-, LDAP- und ALLOW_USER_SIGNUPS-Variablen. |
ALLOW_USER_SIGNUPS |
disabled, withToken (Registrierung über Einladungslinks) oder open. |
SMTP_HOST, SMTP_PORT, SMTP_FROM, SMTP_USER, SMTP_PASSWORD, SMTP_TLS |
Mailserver für Einmal-Anmeldelinks und Benachrichtigungen. |
LDAP_ENABLED=true |
Synchronisiert Benutzer und Gruppen aus einem LDAP-Verzeichnis. |
Das Audit-Protokoll zeigt unter “Persönlich” deine Aktivitäten und unter “Global” die aller Benutzer: Anmeldungen, neue Passkeys, Client-Freigaben. Mit TRUST_PROXY=true stehen dort die echten IP-Adressen. Der Reiter “Global” ist für Admins gedacht.
Backup und Update
Pocket ID speichert alles im Ordner data. Sichere ihn zusammen mit der .env, denn ohne ENCRYPTION_KEY ist die Datenbank nicht zu gebrauchen:
cd ~
sudo tar czf pocketid-backup.tar.gz pocketid/data pocketid/.envZum Aktualisieren ziehst du neue Images und startest neu:
cd ~/pocketid
sudo docker compose pull
sudo docker compose up -dDie Version siehst du unten auf jeder Seite von Pocket ID. Vor größeren Sprüngen lohnt ein Blick in die Release Notes.
Fazit
Pocket ID macht aus vielen einzelnen Logins einen. Die Installation besteht aus einem Container, die Anmeldung läuft über Passkeys, und neue Apps hängst du in wenigen Minuten dran. Mit Gruppen steuerst du, wer was sehen darf, und mit oauth2-proxy bekommen sogar Seiten ohne eigenes Login einen Schutz.
Nutzt du schon eine zentrale Anmeldung für deine Server-Apps, und welche App würdest du als Nächstes anbinden? Schreib es gerne in die Kommentare.
FAQ
Häufige Fragen
Was ist Pocket ID?
Pocket ID ist ein selbst gehosteter OpenID-Connect-Anbieter, bei dem du dich ausschließlich mit Passkeys anmeldest. Andere Apps nutzen Pocket ID für ihre Anmeldung, du brauchst dich nur einmal zu authentifizieren.
Ist Pocket ID kostenlos?
Ja. Pocket ID ist Open Source. Für das Self-Hosting zahlst du nur für den Server.
Brauche ich zwingend eine Domain und HTTPS?
Ja. Browser erlauben Passkeys nur über HTTPS. Nur zum Ausprobieren funktioniert auch localhost.
Was passiert, wenn ich meinen Passkey verliere?
Du kommst nicht mehr an dein Konto. Hinterlege deshalb mindestens zwei Passkeys auf verschiedenen Geräten oder in einem Passwortmanager mit Synchronisierung.
Kann ich bestehende Passwort-Logins der Apps weiter nutzen?
Ja. Die Anmeldung über Pocket ID kommt als zusätzliche Option dazu. Das Passwortformular blendest du in den meisten Apps erst aus, wenn der SSO-Login sicher läuft.
Wie schütze ich eine App, die kein OIDC kann?
Setze oauth2-proxy davor und binde es in nginx oder einen anderen Reverse Proxy ein. Das Beispiel mit nginx und auth_request oben zeigt die komplette Konfiguration.
Warum bekomme ich in Paperless-ngx nach dem Login Fehler?
Neue Benutzer, die sich per Pocket ID anlegen, haben in Paperless zunächst keine Rechte. Gib dem Benutzer als Admin Rechte, setze ihn auf Superuser oder weise mit PAPERLESS_SOCIAL_ACCOUNT_DEFAULT_GROUPS automatisch eine Gruppe zu.
Wie aktualisiere ich Pocket ID?
Im Ordner mit der compose.yaml führst du sudo docker compose pull und danach sudo docker compose up -d aus. Deine Daten im Ordner data bleiben erhalten.