Nginx Proxy Manager einrichten: Wildcard-Zertifikate, Radarr, Pocket ID und eine Anmeldung davor
Für die ersten drei Dienste ist ein Reverse Proxy mit Oberfläche tatsächlich die Abkürzung, als die er beworben wird. Danach fängt die Arbeit an: ein Wildcard-Zertifikat für Dienste, die von außen nie erreichbar sind, eine Anwendung, die hinter dem Proxy plötzlich 502 wirft, und die Frage, wer eigentlich vor der Tür steht. Diese Anleitung geht den Weg vom leeren Server bis zum abgesicherten Stack mit eigenem Anmeldedienst, mit jeder Zeile zum Kopieren.
Was am Ende bei dir läuft
- Ein Proxy im eigenen Docker-Netz, dessen Oberfläche aus dem Internet nicht erreichbar ist.
- Ein Wildcard-Zertifikat per DNS-Challenge, das sich selbst erneuert und auch für Dienste gilt, die niemals nach außen zeigen.
- Sechs typische Dienste dahinter, jeweils mit der einen Einstellung, an der es sonst hängt: Radarr, Pocket ID, Immich, Jellyfin, qBittorrent, Home Assistant.
- Eine Anmeldung per Passkey vor allem, was selbst keine mitbringt.
- Eine 502-Diagnose, die in zwei Minuten zum Ergebnis führt statt in zwei Abenden.
Vorausgesetzt: ein Linux-Server mit Docker und Docker Compose, eine eigene Domain, deren DNS über eine Programmierschnittstelle erreichbar ist (Cloudflare, Hetzner, INWX, netcup und 82 weitere), sowie freie Ports 80 und 443. An Vorkenntnissen genügen docker compose up und ein Texteditor auf dem Server. nginx musst du vorher nicht können, lesen können solltest du es danach.
Die Oberfläche hat sieben Schalter, die Datei hat vierzig Zeilen
Der Nginx Proxy Manager ist kein eigener Webserver. Er ist eine Oberfläche über OpenResty, einer nginx-Variante, dazu Certbot für die Zertifikate und eine Vorlagen-Maschine, die aus jedem angelegten Proxy-Host eine echte nginx-Konfiguration schreibt: eine nummerierte Datei unter /data/nginx/proxy_host/. Version 2.15.1 vom 3. Juni 2026 bringt OpenResty 1.29.2.5 und Certbot 5.6.0 mit.
Diese Trennung erklärt beides: warum das Werkzeug so angenehm ist, und warum es an einem bestimmten Punkt aufhört zu helfen. Domain auf einen Container zeigen, Zertifikat holen, HTTP/2 anhaken: drei Klicks, fertig. Sobald aber ein Dienst eine größere Puffergröße braucht, einen zusätzlichen Header erwartet oder Uploads jenseits von zwei Gigabyte annehmen soll, schreibst du wieder nginx. Und zwar in ein Textfeld mit der Beschriftung „Advanced", ohne Syntaxhervorhebung und ohne einen Hinweis darauf, an welcher Stelle der erzeugten Datei dein Text eigentlich landet.
Was die Oberfläche abnimmt und was nicht
Die obere Hälfte ist der Grund, warum man das Werkzeug nimmt. Die untere Hälfte ist dieser Beitrag.
| Aufgabe | In der Oberfläche | Was du selbst tust |
|---|---|---|
| Domain auf einen Dienst zeigen | Formular mit vier Feldern | nichts |
| Zertifikat holen und erneuern | ein Klick, Erneuerung automatisch | nichts |
| Wildcard über DNS | Auswahl aus 86 Anbietern | Zugangsdaten hinterlegen |
| WebSockets, HSTS, HTTP/2 | je ein Haken | nichts |
| Passwort und IP-Sperre | Access List | den Satisfy-Modus verstehen |
| Uploadgrenze, Timeouts, Puffer | nicht vorgesehen | Advanced-Feld |
| Header setzen oder ändern | nicht vorgesehen | eigener location-Block |
| Anmeldung vor Dienste legen | nicht vorgesehen | zweiter Dienst plus auth_request |
Stand Version 2.15.1. Eine Anmeldung vor Dienste ohne eigenen Login zu legen, ist die einzige Zeile, für die ein zweiter Dienst nötig ist.
Die untere Hälfte der Tabelle ist kein Versäumnis des Projekts. Ein Reverse Proxy soll weiterleiten, und alles, was darüber hinausgeht, gehört in eine Konfigurationsdatei, die jemand gelesen hat. Nur wird das selten so gesagt, und deshalb kursieren Anleitungen, die drei Zeilen in das Advanced-Feld setzen, von denen zwei wirkungslos sind. Warum sie wirkungslos sind, steht weiter unten und ist der eigentliche Grund für diesen Beitrag.
Schritt 1: Eine Installation, die man in zwei Jahren nicht bereut
Die verbreitete Compose-Datei aus der Dokumentation läuft, hat aber zwei Eigenschaften, die man später teuer bezahlt: Sie veröffentlicht die Verwaltungsoberfläche auf allen Netzwerkschnittstellen, und sie benutzt das bewegliche Bild latest. Beides ändern wir gleich zu Beginn, weil es nachträglich niemand mehr anfasst.
services:
app:
# Feste Version statt latest: ein Neustart soll keine Hauptversion ziehen.
image: 'jc21/nginx-proxy-manager:2.15.1'
restart: unless-stopped
ports:
- '80:80'
- '443:443'
# Die Oberfläche hört nur auf dem Server selbst. Zugriff über
# SSH-Tunnel, VPN oder Tailscale, nicht aus dem Internet.
- '127.0.0.1:81:81'
environment:
TZ: 'Europe/Berlin'
# Ohne diese Variablen erscheint beim ersten Aufruf der Assistent.
# INITIAL_ADMIN_EMAIL: 'admin@example.de'
# INITIAL_ADMIN_PASSWORD: 'beim-ersten-login-aendern'
volumes:
- ./data:/data
- ./letsencrypt:/etc/letsencrypt
healthcheck:
test: ['CMD', '/usr/bin/check-health']
interval: 30s
timeout: 3s
networks:
default:
name: proxy
external: true Drei Zeilen verdienen eine Erklärung. 127.0.0.1:81:81 bindet die Oberfläche ausschließlich an den Server selbst: Du erreichst sie danach über einen SSH-Tunnel (ssh -L 8181:127.0.0.1:81 server), über ein VPN oder über Tailscale, aus dem Internet gar nicht. Das feste Bild 2.15.1 sorgt dafür, dass ein Neustart nicht unangekündigt eine neue Hauptversion zieht. Und das externe Netz proxy ist die Voraussetzung dafür, dass die Dienste dahinter überhaupt keinen Port mehr veröffentlichen müssen.
services:
radarr:
image: lscr.io/linuxserver/radarr:latest
restart: unless-stopped
ports:
# Damit hängt Radarr am Port des Servers und ist an jeder
# Absicherung im Proxy vorbei erreichbar.
- '7878:7878'
volumes:
- ./radarr:/config Der Unterschied zwischen den beiden Reitern ist der wichtigste Handgriff dieses ganzen Kapitels. In der ersten Variante hängt Radarr an Port 7878 des Servers und ist damit für jeden erreichbar, der die IP kennt, komplett am Proxy vorbei. In der zweiten Variante existiert dieser Port nicht mehr nach außen; erreichbar ist der Dienst nur noch unter dem Namen radarr innerhalb des Docker-Netzes, und der einzige Container in diesem Netz mit einem offenen Port ist der Proxy.
Der erste Start dauert ein paar Minuten, weil der Container Schlüssel erzeugt, die Datenbank anlegt und die IP-Bereiche von Cloudflare und CloudFront herunterlädt. Letzteres lässt sich mit IP_RANGES_FETCH_ENABLED: 'false' abschalten, kostet dich dann aber die korrekte Besucher-IP hinter einem dieser Anbieter. Zwei weitere Variablen sind gelegentlich nötig: DISABLE_IPV6: 'true', wenn im Log „Address family not supported by protocol" auftaucht, und TZ, damit die Zeitstempel in den Logs zur eigenen Uhr passen.
Schritt 2: Was aus deinen Klicks wird
Bevor wir etwas Fortgeschrittenes bauen, lohnt ein Blick in die Datei, die der Proxy aus einem Formular erzeugt. Wer diese fünf Ebenen einmal gesehen hat, versteht in Sekunden, warum eine Zeile an der falschen Stelle nichts bewirkt, und muss danach nie wieder raten.
Wo deine Klicks in der erzeugten Datei landen
Der Nginx Proxy Manager schreibt aus jedem Proxy-Host eine echte nginx-Konfiguration. Fünf Ebenen, und nur eine davon ist das Advanced-Feld. Klick dich durch, dann weißt du, warum eine Zeile an der falschen Stelle wirkungslos bleibt.
Datei /etc/nginx/nginx.conf
Die globale Ebene steckt im Image und wird bei jedem Update überschrieben. Hier stehen die Vorgaben, die vielen Anleitungen entgehen: Uploads sind bereits bis 2 GB erlaubt, Antworten dürfen 90 Sekunden dauern, und NPM bittet die Anwendung dahinter ausdrücklich um unkomprimierte Daten.
client_max_body_size 2000m;
proxy_read_timeout 90s;
proxy_set_header Accept-Encoding "";
set_real_ip_from 172.16.0.0/12; # Docker-Netze
real_ip_header X-Real-IP;
real_ip_recursive on;Das änderst du nicht hier Eigene globale Zeilen gehören in /data/nginx/custom/http.conf. Diese Datei liest NPM am Ende des http-Blocks ein und ein Update fasst sie nicht an.
Die entscheidende Regel steckt in der letzten Ebene und stammt nicht vom Nginx Proxy Manager, sondern von nginx selbst: proxy_set_header wird nicht ergänzt, sondern ersetzt. Sobald in einem location-Block auch nur eine einzige dieser Direktiven steht, gilt dort ausschließlich die dortige Liste und nichts von weiter oben. Die Vorlage bindet in location / die Datei proxy.conf mit sechs solchen Zeilen ein. Jeder Header, den du im Advanced-Feld setzt, wird damit still verworfen.
Der Ausweg ist kein Trick, sondern eine dokumentierte Eigenschaft: Beginnt dein Text im Advanced-Feld mit location / {, erkennt NPM das und lässt seinen eigenen Standardblock weg. Deiner ist dann der einzige, und du bist für alles darin selbst zuständig, einschließlich der Weiterleitung.
# Beginnt der Text mit dieser Zeile, lässt NPM seinen eigenen
# location-/-Block weg und dieser hier gilt allein.
location / {
proxy_pass $forward_scheme://$server:$port$request_uri;
# Vollständig wiederholen: proxy_set_header wird nicht ergänzt,
# sondern auf dieser Ebene komplett ersetzt.
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Der eigentliche Grund für den ganzen Block.
proxy_set_header X-Forwarded-Ssl on;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}Der Reiter „Custom locations" nimmt dir diese Arbeit für zusätzliche Pfade ab, hat aber eine Eigenheit, die man kennen muss: Dort trägt die Vorlage den Zielnamen fest in die Datei ein, statt ihn wie im Standardblock über eine Variable aufzulösen. Fest eingetragene Namen löst nginx beim Start auf. Läuft der Zielcontainer gerade nicht, verweigert nginx den Dienst mit „host not found in upstream", und der Host geht offline. Der Standardblock dagegen fragt zur Laufzeit nach und merkt binnen zehn Sekunden, wenn ein Container neu gestartet wurde und eine andere IP hat.
Schritt 3: Ein Wildcard-Zertifikat per DNS-Challenge
Der übliche Weg zu einem Zertifikat ist die HTTP-Challenge: Let’s Encrypt ruft deine Domain auf Port 80 auf und erwartet dort eine Datei. Das setzt voraus, dass die Domain aus dem Internet erreichbar ist, und es liefert niemals ein Wildcard-Zertifikat. Für einen Heimserver mit zwölf internen Diensten ist beides hinderlich.
Die DNS-Challenge dreht das um. Statt einer Datei legt Certbot über die Schnittstelle deines DNS-Anbieters einen TXT-Eintrag an, und Let’s Encrypt fragt diesen Eintrag ab. Dein Server muss dafür aus dem Internet überhaupt nicht erreichbar sein. Damit bekommst du ein gültiges Zertifikat für *.intern.example.de, das für jeden Dienst gilt, den du je anlegst, auch für die, die nur im Heimnetz auflösen. NPM kennt dafür 86 Anbieter, darunter Cloudflare, Hetzner, INWX, netcup, IONOS und deSEC.
# Cloudflare API token
dns_cloudflare_api_token = 0123456789abcdef0123456789abcdef01234567Zwei Felder darunter stehen die Wartesekunden. Der Standardwert genügt großen Anbietern, kleinen nicht: Wenn die Ausstellung mit „DNS problem: NXDOMAIN" scheitert, obwohl der Eintrag später sichtbar ist, hat Certbot zu früh nachgesehen. 120 Sekunden lösen das in fast allen Fällen. Wichtiger als der Wert ist die Reihenfolge: Erst ein Testzertifikat gegen die Staging-Umgebung anfordern, dann das echte. Let’s Encrypt begrenzt fehlgeschlagene Versuche pro Woche, und diese Grenze ist an einem Abend Fehlersuche erreicht.
Scheitert eine Erneuerung nach einem Update mit einer Fehlermeldung aus pip, ist die Erweiterung nicht sauber installiert. Dafür bringt das Bild ein Skript mit, das genau das nachholt, ohne dass du den Container neu bauen musst:
# Containername herausfinden
docker compose ps
# Erweiterung für einen einzelnen Anbieter nachinstallieren
docker exec npm-app-1 /command/s6-setuidgid 1000:1000 \
bash -c "/app/scripts/install-certbot-plugins cloudflare"
# Danach die Logs mitlesen und die Erneuerung erneut anstoßen
docker compose logs -f appSchritt 4: Die Dienste dahinter
Ein Proxy-Host ist in dreißig Sekunden angelegt: Domain, Schema, Zielname, Port, Zertifikat auswählen, „Force SSL" anhaken. Was danach nicht funktioniert, liegt fast nie am Proxy, sondern an einer Einstellung in der Anwendung dahinter, und die ist pro Anwendung eine andere. Die folgende Übersicht ist die Zusammenfassung von sechs Abenden Forensuche.
Sechs Dienste, sechs verschiedene Eigenheiten
Ein Proxy-Host ist in dreißig Sekunden angelegt. Was danach nicht funktioniert, liegt fast immer an einer Einstellung in der Anwendung dahinter. Hier steht pro Dienst, welche das ist.
Die *arr-Anwendungen halten die Oberfläche über SignalR aktuell, also über eine dauerhafte Verbindung. Ohne den WebSocket-Haken lädt die Seite, aber die Warteschlange aktualisiert sich nie und im Browser-Log stapeln sich abgebrochene Verbindungen. Sonarr hört auf 8989, Prowlarr auf 9696, Lidarr auf 8686, Bazarr auf 6767.
- Weiterleitung
http · radarr · 7878- WebSockets
- Pflicht
- Block Common Exploits
- kann an bleiben
- Einstellung in der App
- Authentifizierung auf „Enabled", nicht auf „Disabled for Local Addresses"
Kein Eintrag im Advanced-Feld nötig. Die Voreinstellungen genügen.
Stolperstein Die Einstellung „Disabled for Local Addresses" prüft die IP des Aufrufers. Hinter dem Proxy ist das die IP des Proxys, und die ist immer lokal. Damit ist die Anmeldung für alle abgeschaltet, auch für das halbe Internet. Genau dafür gibt es CVE-2026-30975 in Sonarr.
Zwei Muster wiederholen sich darin. Erstens: Fast jede moderne Oberfläche hält eine dauerhafte Verbindung offen, um sich selbst zu aktualisieren. Der Haken „Websockets Support" ist deshalb eher die Regel als die Ausnahme, und sein Fehlen äußert sich nicht als Fehlermeldung, sondern als eine Seite, die einfach nie neuere Daten zeigt. Zweitens: Anwendungen, die etwas auf ihre eigene Adresse geben, müssen wissen, dass ein Proxy davorsteht. Home Assistant will das explizit in seiner Konfigurationsdatei hören, Jellyfin unter „Known proxies", Pocket ID über TRUST_PROXY, qBittorrent über „Server domains". Vier Dienste, vier Orte, dieselbe Frage.
Bleibt die Entscheidung zwischen einer Subdomain je Dienst (radarr.example.de) und einem Unterordner (example.de/radarr). Nimm Subdomains. Sie kosten dank Wildcard-Zertifikat nichts extra, jeder Dienst bekommt eigene Cookies, und du sparst dir die Basis-URL, die in jeder Anwendung anders heißt und die erfahrungsgemäß irgendwann jemand zurücksetzt. Unterordner lohnen sich nur, wenn du genau eine Domain hast und keine weitere bekommen kannst.
Schritt 5: Radarr, Sonarr und der Fehler, der Türen öffnet
Die *arr-Anwendungen sind der häufigste Anlass, überhaupt einen Reverse Proxy aufzusetzen, und gleichzeitig der Ort mit der gefährlichsten Fehlkonfiguration. Sie hat einen Namen: „Authentication Required" steht auf „Disabled for Local Addresses".
Diese Einstellung prüft die Adresse des Aufrufers und verzichtet bei lokalen Adressen auf die Anmeldung. Hinter einem Reverse Proxy ist die Adresse des Aufrufers aber immer die des Proxys, und die ist immer lokal. Das Ergebnis ist eine Oberfläche, die ohne Passwort öffnet, für dich im Wohnzimmer wie für jeden anderen. Sonarr hat dafür im Jahr 2026 einen Sicherheitshinweis veröffentlicht, CVE-2026-30975 mit Bewertung 8.1, samt der eindeutigen Empfehlung: Die Einstellung gehört auf „Enabled", immer.
Der zweite typische Fall betrifft die Kombination aus Zugriffsliste und Programmierschnittstelle. Legst du im Proxy eine Access List mit Passwort an, wandert diese Abfrage in den Block location /, und damit scheitern alle Anwendungen, die sich sonst mit ihrem API-Schlüssel melden: Overseerr, Prowlarr, die Handy-App, dein eigenes Skript. Weil nginx den längeren Pfad gewinnen lässt, genügt ein zusätzlicher Block für die Schnittstelle.
# nginx nimmt den längsten passenden Pfad. /api gewinnt damit gegen /
# und erbt die Passwortabfrage nicht, die nur im Standardblock steht.
location /api {
proxy_pass $forward_scheme://$server:$port$request_uri;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# SignalR hält die Verbindung offen, auch auf diesem Pfad.
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}Der API-Schlüssel bleibt damit die einzige Hürde vor der Schnittstelle. Das ist vertretbar, solange er lang und pro Anwendung verschieden ist, und es ist der Grund, warum der vorherige Absatz auf „Enabled" besteht: Ohne Anmeldung in der Anwendung selbst wäre dieser Block ein offenes Tor.
Schritt 6: Pocket ID als eigener Anmeldedienst
Pocket ID ist ein OIDC-Anbieter, der ausschließlich mit Passkeys anmeldet: kein Passwort, kein zweiter Faktor per SMS, sondern Fingerabdruck oder Gesichtserkennung auf dem Gerät, das ohnehin in der Hand liegt. Er ist klein genug, um ihn selbst zu betreiben, und genau das macht ihn für einen eigenen Server interessant. Die aktuelle Version 2.13.0 stammt vom 7. August 2026.
services:
pocket-id:
image: pocketid/pocket-id:v2
restart: unless-stopped
# Kein ports-Eintrag: der Proxy erreicht den Dienst im Netz "proxy"
# unter pocket-id:1411.
environment:
# Genau die Adresse, die der Browser sieht. Samt https.
APP_URL: 'https://id.example.de'
# Die Netze, deren weitergereichte Header geglaubt werden dürfen.
TRUST_PROXY: '172.18.0.0/16'
# Einmalig erzeugen: openssl rand -base64 32
ENCRYPTION_KEY_FILE: /run/secrets/pocket_id_key
volumes:
- ./data:/app/data
secrets:
- pocket_id_key
healthcheck:
test: ['CMD', '/app/pocket-id', 'healthcheck']
interval: 90s
secrets:
pocket_id_key:
file: ./.secrets/pocket_id_key.txt
networks:
default:
name: proxy
external: trueTRUST_PROXY nennt die Netze, deren weitergereichte Header geglaubt werden dürfen. Ohne diese Angabe sieht Pocket ID bei jedem Zugriff die IP des Proxys, und damit greifen Zählwerke gegen zu viele Fehlversuche für alle Nutzer gemeinsam statt pro Angreifer. true vertraut allen Absendern und ist nur vertretbar, wenn der Dienst wirklich keinen anderen Weg nach außen hat; sauberer ist das Docker-Netz als CIDR.
Der Proxy-Host dazu ist unspektakulär, bis auf drei Zeilen im Advanced-Feld. Die Oberfläche ist mit SvelteKit gebaut und schickt größere Header, als nginx voreingestellt puffert. Ohne die Vergrößerung antwortet der Proxy mit 502, und zwar erst nach erfolgreicher Anmeldung, was die Suche in die falsche Richtung schickt.
# Die Oberfläche ist mit SvelteKit gebaut und schickt große Header.
# Ohne diese drei Zeilen: 502, und zwar erst nach dem Login.
proxy_buffer_size 256k;
proxy_buffers 4 512k;
proxy_busy_buffers_size 512k; Nach dem Start legst du unter /setup das erste Konto an und registrierst dort deinen Passkey. Danach folgt der Teil, der Pocket ID nützlich macht: Unter „OIDC Clients" entsteht für jede Anwendung, die sich hier anmelden lassen soll, ein Eintrag mit Kennung, Geheimnis und Rücksprungadresse.
Schritt 7: Eine Anmeldung vor alles legen
Jetzt fügt sich beides zusammen. Viele Selfhosting-Dienste haben gar keine Benutzerverwaltung, andere eine, der man nicht zutrauen möchte, allein im Internet zu stehen. Für solche Fälle setzt man eine Anmeldung davor, die der Proxy vor jeder Anfrage befragt. In nginx heißt dieses Verfahren auth_request: eine interne Unteranfrage, deren Antwortcode darüber entscheidet, ob die eigentliche Anfrage weiterläuft.
Was fehlt, ist der Dienst, der diese Unteranfrage beantwortet. Tinyauth (Version 5.1.3) übernimmt das, spricht selbst OIDC und kann Pocket ID als Anmeldequelle benutzen. Der Ablauf am Ende: Du rufst Radarr auf, der Proxy fragt Tinyauth, Tinyauth schickt dich zu Pocket ID, dort hältst du den Finger auf das Telefon, und du landest in Radarr.
services:
tinyauth:
image: ghcr.io/tinyauthapp/tinyauth:v5
restart: unless-stopped
environment:
TINYAUTH_APPURL: 'https://auth.example.de'
# Nur von diesem Netz aus wird die Absender-IP geglaubt.
TINYAUTH_AUTH_TRUSTEDPROXIES: '172.18.0.0/16'
# Pocket ID als Anmeldequelle. Die Kennung "pocketid" taucht in
# der Rücksprungadresse wieder auf und muss dort gleich lauten.
TINYAUTH_OAUTH_PROVIDERS_POCKETID_NAME: 'Pocket ID'
TINYAUTH_OAUTH_PROVIDERS_POCKETID_CLIENTID: 'aus-pocket-id'
TINYAUTH_OAUTH_PROVIDERS_POCKETID_CLIENTSECRET: 'aus-pocket-id'
TINYAUTH_OAUTH_PROVIDERS_POCKETID_AUTHURL: 'https://id.example.de/authorize'
TINYAUTH_OAUTH_PROVIDERS_POCKETID_TOKENURL: 'https://id.example.de/api/oidc/token'
TINYAUTH_OAUTH_PROVIDERS_POCKETID_USERINFOURL: 'https://id.example.de/api/oidc/userinfo'
TINYAUTH_OAUTH_PROVIDERS_POCKETID_REDIRECTURL: 'https://auth.example.de/api/oauth/callback/pocketid'
TINYAUTH_OAUTH_PROVIDERS_POCKETID_SCOPES: 'openid email profile groups'
# Direkt weiterleiten, statt eine Anbieterauswahl zu zeigen.
TINYAUTH_OAUTH_AUTOREDIRECT: 'pocketid'
networks:
default:
name: proxy
external: true Der zweite Reiter ist der Kern. auth_request ruft bei jeder Anfrage die interne Adresse /tinyauth auf. Kommt 200 zurück, läuft alles weiter; kommt 401 oder 403, schickt error_page ... =302 den Browser an die Anmeldeseite, deren Adresse Tinyauth in einem Antwortkopf mitgeliefert hat. Dieser Umweg über den Antwortkopf existiert, weil nginx anders als Traefik oder Caddy aus einer Unteranfrage heraus nicht selbst weiterleiten kann.
Wer feiner steuern will, legt in Pocket ID Gruppen an und beschränkt einzelne Anwendungen darauf. Der Zugriff auf die Mediathek für alle im Haus, der Zugriff auf das Verwaltungswerkzeug nur für dich, und beides ohne ein einziges zusätzliches Passwort. Das ist derselbe Aufbau, den wir in Unternehmen einsetzen, dort nur mit einem größeren Anmeldedienst dahinter.
502 Bad Gateway: vier Ursachen, zwei Minuten
502 heißt nicht „etwas ist kaputt", sondern sehr genau: nginx hat das Ziel angesprochen und keine brauchbare Antwort bekommen. Damit bleiben vier Ursachen, und die lassen sich in dieser Reihenfolge in wenigen Minuten abarbeiten.
Erstens die falsche Adresse. Steht im Feld „Forward Hostname" localhost oder 127.0.0.1, zeigt das auf den Proxy-Container selbst, nicht auf den Server. Richtig ist der Dienstname aus dem gemeinsamen Docker-Netz, also radarr. Läuft der Dienst wirklich auf dem Host und nicht in Docker, nimmst du die Adresse der Docker-Brücke, üblicherweise 172.17.0.1.
Zweitens das falsche Netz. Der Name löst nur auf, wenn beide Container dasselbe Netz teilen. Ein Tippfehler im Dienstnamen sieht dabei genauso aus wie ein fehlendes Netz. Drittens das falsche Schema. Spricht der Dienst ausschließlich HTTPS und im Formular steht http, kommt eine Antwort zurück, die nginx nicht versteht. Viertens zu kleine Puffer, der Pocket-ID-Fall von oben: Im Fehlerlog steht dann wörtlich „upstream sent too big header".
# 1. Läuft das Ziel überhaupt?
docker compose ps
# 2. Was sagt der Proxy?
tail -n 50 data/logs/proxy-host-7_error.log
# 3. Die wichtigste Zeile: aus dem Proxy-Container heraus fragen.
# Antwortet hier etwas, liegt der Fehler in der Konfiguration.
# Antwortet nichts, stimmen Name, Port oder Netz nicht.
docker exec npm-app-1 curl -sS -o /dev/null -w '%{http_code}\n' http://radarr:7878/
# 4. Teilen sich beide Container wirklich dasselbe Netz?
docker network inspect proxy --format '{{ range .Containers }}{{ .Name }} {{ end }}' Ein Sonderfall ist der nahe Verwandte 504. Der bedeutet, dass die Antwort zu lange gedauert hat, und die Voreinstellung dafür liegt bei 90 Sekunden. Große Uploads, Datenbank-Migrationen nach einem Update und Server-Sent-Events laufen regelmäßig darüber. Abhilfe ist ein höherer proxy_read_timeout im Advanced-Feld. Bei fortlaufenden Datenströmen gehört proxy_buffering off dazu, sonst sammelt der Proxy die Häppchen und gibt sie erst am Schluss aus. Dieselbe Zeile brauchst du übrigens für einen selbst gebauten MCP-Server, und aus demselben Grund.
Betrieb: was nach dem Livegang wichtig wird
Ein Reverse Proxy ist der eine Dienst, dessen Ausfall alle anderen mitnimmt, und gleichzeitig der einzige, den man von außen erreicht. Beides zusammen macht die folgende Liste zu einer, die man einmal durchgeht und danach nie bereut.
Sieben Punkte, die nach dem ersten Vorfall auf jeder Liste stehen
Zwei Dinge, die auf keiner Checkliste stehen, aber im Alltag Zeit sparen. Erstens: Die Zugriffs- und Fehlerlogs liegen pro Host getrennt unter /data/logs/proxy-host-<id>_access.log. Die Nummer ist die ID aus dem Dateinamen der Konfiguration, und sie steht nirgends in der Oberfläche; du findest sie, indem du den Host bearbeitest und in die Adresse des Browsers schaust. Zweitens: Diese Logs enthalten IP-Adressen und sind damit personenbezogene Daten. Wer den Proxy geschäftlich betreibt, braucht dafür eine Aufbewahrungsfrist im Verzeichnis der Verarbeitungstätigkeiten. Die mitgelieferte Rotation hält Zugriffslogs vier Wochen und Fehlerlogs zehn, und sie lässt sich über eine eigene Konfigurationsdatei ersetzen.
Aus der Praxis: derselbe Aufbau, andere Einsätze
Was hier nach Heimserver klingt, ist in kleineren Betrieben oft die gesamte Infrastruktur. Ein Handwerksbetrieb mit einem Server im Technikraum betreibt darauf typischerweise vier bis sechs Dienste: Dokumentenablage, Zeiterfassung, ein Dashboard, gelegentlich ein Sprachmodell. Genau ein Dienst davon muss von außen erreichbar sein, meistens die Terminbuchung, alle anderen nur aus dem Büro und aus dem VPN.
Diese Trennung ist mit einem Proxy und einem Wildcard-Zertifikat billiger zu haben als mit irgendeiner anderen Lösung: ein Zertifikat, eine Stelle für TLS, eine Stelle für Zugriffsregeln, und die Dienste selbst veröffentlichen keinen einzigen Port. Wie so etwas beim Kunden aussieht, steht in unseren Case Studies, etwa bei Reifen Allgäu, wo die Online-Terminbuchung gegen den echten Kalender arbeitet und deshalb als einziger Dienst nach außen zeigt.
Fazit
Der Nginx Proxy Manager nimmt dir die Zertifikate ab und macht das Weiterleiten zu einem Formular. Alles darüber hinaus bleibt nginx, und das ist keine Schwäche, sondern die Bedingung dafür, dass du es überhaupt beeinflussen kannst. Wer die fünf Ebenen aus Schritt 2 im Kopf hat, schreibt die drei Zeilen im Advanced-Feld richtig statt dreimal falsch.
Wenn du weitermachen willst, in dieser Reihenfolge: Erst das Wildcard-Zertifikat, weil es jede spätere Domain kostenlos mitnimmt. Dann die Anmeldung vor allem, was keine eigene hat, denn ein Dienst ohne Login im Internet ist eine Frage der Zeit und nicht des Glücks. Und erst danach mehr Dienste, denn sechs sauber abgesicherte sind im Alltag deutlich brauchbarer als zwanzig, bei denen niemand mehr weiß, welcher davon offen steht.