LibreTV mit Docker auf Ubuntu: Installation hinter Reverse Proxy
LibreTV 2.19.5 per Docker Compose hinter Caddy oder Nginx betreiben: Passwort, PROXY_SECRET, Secure-Cookie über HTTPS, eigene Live-Quellen und die Rechtslage.
WerbelinksMit * markierte Links sind Werbelinks: Bei einem Kauf erhalten wir eine Provision, der Preis bleibt gleich. Als Amazon-Partner verdiene ich an qualifizierten Verkäufen. Mehr dazu

LibreTV mit Docker ist schnell installiert: ein Container, ein Passwort, Port 8080. Damit der Login sicher funktioniert und das Passwort nicht im Klartext durchs Netz geht, gehört ein Reverse Proxy mit HTTPS davor. Diese Anleitung zeigt die Einrichtung von LibreTV 2.19.5 auf einem Ubuntu-Server mit Docker Compose und Caddy, erklärt das Zusammenspiel von Secure-Cookie, X-Forwarded-Proto und COOKIE_SECURE und ordnet die rechtliche Seite ein.
Voraussetzungen
- Server mit Ubuntu 24.04 LTS (oder Debian 12), x86_64 oder ARM64. Das Image wird laut README für
linux/amd64undlinux/arm64gebaut; der Docker-Teil dieser Anleitung ist nicht an eine Distribution gebunden. - Ein kleiner Server genügt: Im Test belegte LibreTV nach dem Start rund 48 MiB RAM, Caddy rund 68 MiB. Live-Streams über den LibreTV-Proxy (Schritt 7) kosten zusätzlich Bandbreite.
- Docker Engine mit Docker Compose Plugin. Auf Ubuntu installieren Sie beides nach der offiziellen Docker-Anleitung für Ubuntu aus dem apt-Repository von Docker; Pakete wie
docker.iooderdocker-compose-v2entfernen Sie laut Doku vorher. Siehe auch die Docker- und Compose-Grundlage. - Eine (Sub-)Domain, deren DNS-Eintrag auf den Server zeigt, und die Ports 80 und 443 frei. Wichtig bei ufw: Laut Docker-Doku umgehen veröffentlichte Container-Ports die ufw-Regeln; LibreTV lauscht hier deshalb nur auf 127.0.0.1. Für ein reines Heimnetz genügt ein interner Name mit internem Zertifikat.
- Eigene oder lizenzierte Quellen, siehe den rechtlichen Hinweis weiter unten.
Eckdaten im Überblick
| Punkt | Wert |
|---|---|
| Projekt, Lizenz | LibreSpark/LibreTV, AGPL-3.0, Next.js 15 (getestet 2.19.5 mit Next.js 15.5.26, Node.js 22.23.3) |
| Image | ghcr.io/librespark/libretv, Spiegel docker.io/bestzwei/libretv; Tags latest, 2.19, 2.19.5 (am 18.10.2026 identischer Digest, für 2.19.5 auch auf Docker Hub) |
| Port im Container | 8080 (HTTP) |
| Volumes | keine; Verlauf und Einstellungen liegen im Browser |
| Pflicht-Variable | PASSWORD |
| Empfohlen | PROXY_SECRET (Signaturschlüssel der Sitzungen) |
| Optional | COOKIE_SECURE, LIVE_ALLOW_PRIVATE, REQUEST_TIMEOUT, DEFAULT_LIVE_SOURCES |
| Benutzer im Container | nextjs (UID 100), kein root |
Rechtlicher Hinweis: welche Quellen erlaubt sind
LibreTV speichert selbst keine Videos. Laut README ist es ein Aggregator für sogenannte Sammelseiten (Apple-CMS-Schnittstellen) und für M3U-Listen, und für die Rechtmäßigkeit der Inhalte sind die jeweiligen Quellen verantwortlich. Fremde Quellen nennt diese Anleitung bewusst nicht.
Für Deutschland und die EU ist die Lage klar: Der Europäische Gerichtshof hat im Urteil C-527/15 „Filmspeler“ vom 26. April 2017 entschieden, dass vorübergehende Vervielfältigungen durch Streaming von der Website eines Dritten, auf der ein Werk ohne Erlaubnis des Rechteinhabers angeboten wird, auf einem multimedialen Medienabspieler nicht unter die Ausnahme des Art. 5 Abs. 1 und 5 der Richtlinie 2001/29/EG fallen. Die Privatkopie nach § 53 Abs. 1 UrhG ist ausgeschlossen, wenn eine „offensichtlich rechtswidrig hergestellte oder öffentlich zugänglich gemachte Vorlage“ verwendet wird. § 106 UrhG stellt das Vervielfältigen, Verbreiten oder öffentliche Wiedergeben ohne Einwilligung des Berechtigten unter Strafe.
Sinnvoll und zulässig ist LibreTV deshalb nur mit Quellen, für die Rechte bestehen: eigene Aufnahmen und Medien, die Sie selbst als HLS bereitstellen, frei lizenzierte Streams oder ein IPTV-Angebot, das Sie vertraglich nutzen dürfen. Für eine reine Mediathek eigener Filme ist ein Medienserver wie Jellyfin mit Docker oft die bessere Wahl. Der Funktionstest in dieser Anleitung lief ausschließlich mit einem selbst erzeugten Testbild-Stream.
Schritt 1: Projektordner und Geheimnisse anlegen
Legen Sie einen Ordner an und erzeugen Sie Passwort und Signaturschlüssel. Ohne PROXY_SECRET leitet LibreTV den Schlüssel laut Quellcode aus dem Passwort ab. Ein eigener Wert trennt beides.
sudo mkdir -p /opt/libretv && sudo chown "$USER": /opt/libretv && cd /opt/libretv
cat > .env <<EOF
PASSWORD=$(openssl rand -base64 18)
PROXY_SECRET=$(openssl rand -hex 32)
EOF
chmod 600 .env
grep PASSWORD .env
Das Passwort ist das einzige Zugangsmerkmal der Instanz; es gibt keine Benutzerkonten. Alle, die das Passwort kennen, sehen dieselbe Oberfläche, die Quellen speichert aber jeder Browser für sich.
Verifizieren: cat .env zeigt zwei Zeilen mit zufälligen Werten, ls -l .env zeigt die Rechte -rw-------.
Schritt 2: LibreTV mit Docker Compose und Caddy
LibreTV lauscht nur auf 127.0.0.1, von außen ist allein Caddy erreichbar. Das ist mehr als Kosmetik: Die Login-Sperre nach zehn Fehlversuchen richtet sich nach der Client-IP aus X-Forwarded-For. Im Test ließ sich die Sperre direkt auf Port 8080 mit einem gefälschten Header umgehen (wieder 401 statt 429), über Caddy dagegen nicht.
services:
libretv:
image: ghcr.io/librespark/libretv:${LIBRETV_TAG:-2.19.5}
container_name: libretv
restart: unless-stopped
environment:
PASSWORD: ${PASSWORD:?PASSWORD in .env setzen}
PROXY_SECRET: ${PROXY_SECRET:?PROXY_SECRET in .env setzen}
ports:
- "127.0.0.1:8080:8080"
caddy:
image: caddy:2.11
container_name: libretv-caddy
restart: unless-stopped
ports:
- "80:80"
- "443:443"
- "443:443/udp"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy_data:/data
- caddy_config:/config
depends_on:
- libretv
volumes:
caddy_data:
caddy_config:
Die Schreibweise ${PASSWORD:?…} sorgt dafür, dass Compose ohne Passwort gar nicht erst startet. Im Test lautete die Meldung bei fehlender .env: required variable PASSWORD is missing a value: PASSWORD in .env setzen. Der Tag ist fest auf 2.19.5 gesetzt; mit latest würde jeder Pull auf die jeweils neueste Version wechseln.
Verifizieren: docker compose config gibt die zusammengesetzte Konfiguration ohne Fehlermeldung aus; fehlt die .env, bricht der Befehl mit der oben genannten Meldung ab.
Schritt 3: Caddyfile schreiben
Caddy holt für eine öffentlich erreichbare Domain selbst ein Zertifikat und leitet HTTP auf HTTPS um. Ersetzen Sie tv.example.de durch Ihren Namen:
tv.example.de {
reverse_proxy libretv:8080
}
Für einen Server nur im Heimnetz ergänzen Sie in dem Block die Zeile tls internal; Caddy stellt dann ein Zertifikat seiner eigenen Zertifizierungsstelle aus, dem Sie auf den Clients einmal vertrauen müssen. Diese Variante lief im Test. Caddy setzt laut Doku X-Forwarded-Proto und X-Forwarded-For selbst und ignoriert solche Header vom Client. Mehr zu Zertifikaten und mehreren Domains steht in der Anleitung Caddy als Reverse Proxy mit automatischem HTTPS.
Verifizieren: docker run --rm -v "$PWD/Caddyfile:/etc/caddy/Caddyfile:ro" caddy:2.11 caddy validate --config /etc/caddy/Caddyfile endet mit Valid configuration.
Schritt 4: Stack starten und Version prüfen
docker compose pull
docker compose up -d
docker compose ps
docker compose logs libretv
Das Log ist kurz. Im Test stand dort:
▲ Next.js 15.5.26
- Local: http://localhost:8080
- Network: http://0.0.0.0:8080
✓ Starting...
✓ Ready in 116ms
Die laufende Version liefert der Endpunkt /api/status, auch ohne Login:
curl -s http://127.0.0.1:8080/api/status
{"passwordRequired":true,"verified":false,"version":"2.19.5","defaultSources":[],"defaultLiveSources":[],"defaultSubscriptions":[],"defaultRecommendSource":null,"defaultImageMode":null}
Verifizieren: docker compose ps zeigt beide Container als Up, version ist 2.19.5, curl -I http://tv.example.de antwortet mit 308 Permanent Redirect auf HTTPS.
Schritt 5: Login über HTTPS und das Secure-Cookie
Öffnen Sie https://tv.example.de. Die Oberfläche ist chinesisch; vor der Startseite erscheint der Dialog „访问验证“ (Zugangsprüfung) mit einem Passwortfeld und der Schaltfläche „进入“ (Eintreten).

Nach erfolgreichem Login setzt LibreTV das Cookie ltv_session für 90 Tage. Ob es das Secure-Flag trägt, entscheidet Version 2.19.5 so: COOKIE_SECURE=true oder false erzwingt den Wert, sonst zählt der Header X-Forwarded-Proto. Der Warnhinweis im README beschreibt dagegen ein pauschales Secure-Cookie im Produktivbetrieb; der Test ergab folgendes Bild:
| Aufruf | Set-Cookie | Login hält? |
|---|---|---|
| Caddy, HTTPS | Secure; HttpOnly; SameSite=lax | ja |
Nginx mit X-Forwarded-Proto $scheme | Secure; HttpOnly; SameSite=lax | ja |
Nginx mit TLS, ohne X-Forwarded-Proto | HttpOnly; SameSite=lax (kein Secure) | ja, Cookie aber ungeschützt |
direkt http://…:8080, ohne Variable | HttpOnly; SameSite=lax | ja, Passwort im Klartext |
direkt http://…:8080 mit COOKIE_SECURE=true | Secure; HttpOnly; SameSite=lax | nein |
Der letzte Fall ist das bekannte Fehlerbild „Passwort richtig, aber Login klappt nicht“: Der Server antwortet mit 200 und {"success":true}, der Client verwirft das Secure-Cookie auf einer HTTP-Seite, /api/status meldet danach "verified":false. Mit curl lässt sich das nachstellen, wenn Sie einen echten Hostnamen statt localhost verwenden (Browser nehmen localhost von der Regel aus):
curl -s -i --resolve tv.example.test:8080:127.0.0.1 -c jar \
-H 'Content-Type: application/json' -d '{"password":"IHR-PASSWORT"}' \
http://tv.example.test:8080/api/auth | grep -i set-cookie
grep -c ltv_session jar # 0 = Cookie verworfen
Hinter Caddy brauchen Sie COOKIE_SECURE nicht. Setzen Sie COOKIE_SECURE=true laut README dann, wenn ein vorgeschalteter Dienst X-Forwarded-Proto nicht weitergibt und Sie trotzdem ein Secure-Cookie wollen, aber nur, wenn die Seite ausschließlich per HTTPS erreichbar ist.
Verifizieren: Der Login über https:// liefert Set-Cookie: ltv_session=…; Secure; HttpOnly; SameSite=lax, danach zeigt https://tv.example.de/api/status "verified":true.
Schritt 6: Startseite und Einstellungen
Die Einstellungen öffnen Sie über das Zahnrad oben rechts (设置). Das Panel hat drei Reiter: 源管理 (Quellenverwaltung), 偏好设置 (Vorlieben) und 数据 (Daten). Nach der Installation ist die Liste der Videoquellen (点播源) leer.
Die Startseite zeigt ab Werk Ranglisten mit Filmtiteln und Postern aus öffentlichen Diensten. Wer das nicht möchte, schaltet unter 偏好设置, Bereich 首页与内容, den Schalter 首页推荐 (Empfehlungen auf der Startseite) aus. Der Filter für Erwachseneninhalte (成人内容过滤) ist ab Werk aktiv.

Alle Einstellungen und Quellen speichert LibreTV im Browser (localStorage, Verlauf in IndexedDB), nicht auf dem Server. Ein anderer Browser startet daher leer. Zum Übertragen gibt es laut README unter 数据 einen Import und Export der Konfiguration; Administratoren können Live-Quellen laut README zentral über DEFAULT_LIVE_SOURCES vorgeben.
Verifizieren: Nach dem Abschalten zeigt die Startseite nur noch Logo und Suchfeld, ohne Poster.
Schritt 7: Eigenen Stream als Live-Quelle einbinden
Live-Quellen sind M3U-Listen. Für den Test erzeugte ein ffmpeg-Container einen 60 Sekunden langen HLS-Stream mit Testbild (testsrc2, 1280 x 720) und eine passende Liste, ausgeliefert von einem Nginx-Container namens hls im selben Compose-Netz:
#EXTM3U
#EXTINF:-1 tvg-id="test" group-title="Test",Eigener Teststream
http://hls/index.m3u8
Liegt die Quelle in einem privaten Netz (Docker-Netz, LAN, eigener IPTV-Server), blockt LibreTV sie zum Schutz vor SSRF. Der Test lieferte ohne weitere Einstellung 403 mit {"error":"目标地址解析到私有/保留网络"} („Ziel löst auf privates oder reserviertes Netz auf“). Ergänzen Sie für interne Quellen in der compose.yaml unter environment:
LIVE_ALLOW_PRIVATE: "1"
Danach docker compose up -d. Die Freigabe gilt laut Kommentar in der docker-compose.yml des Projekts für Listenabruf, Erreichbarkeitstest und Streamproxy. Setzen Sie sie nur, wenn Sie interne Quellen wirklich brauchen, denn angemeldete Nutzer können LibreTV damit Adressen im internen Netz abrufen lassen.
In der Oberfläche fügen Sie die Liste unter 设置, 源管理, 直播源 (Live-Quellen) mit 添加直播源 hinzu: Adresse der M3U-Datei, optional ein Name. LibreTV lädt die Liste sofort und zeigt die Zahl der Kanäle.

Unter 直播 (Live) oben in der Navigation erscheint der Kanal; ein Klick startet die Wiedergabe. Erreicht der Browser die Quelle nicht direkt, leitet LibreTV den Stream laut README über /api/live/stream durch den Server. Im Test lief so der Stream, der nur im Docker-Netz erreichbar war.

Verifizieren: Die Wiedergabe startet nach einem Klick auf den Kanal; im Test meldete der Erreichbarkeitstest /api/live/probe "ok":true und "level":"segment", und das Video lief nach zehn Sekunden bei Sekunde 9,6 in 1280 x 720.
Schritt 8: Nginx statt Caddy
Betreiben Sie schon Nginx, binden Sie LibreTV dort ein. Entscheidend ist die Zeile mit X-Forwarded-Proto, sonst fehlt dem Cookie das Secure-Flag. proxy_buffering off verhindert, dass Nginx die Stream-Antworten erst zwischenspeichert.
location / {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_buffering off;
}
Im Test lief Nginx als Container im selben Compose-Netz, dort mit proxy_pass http://libretv:8080;. Läuft Nginx direkt auf dem Host, gilt die Adresse 127.0.0.1:8080 wie oben. Den vollständigen Server-Block mit Zertifikat beschreibt die Anleitung Nginx als Reverse Proxy mit TLS. Für Traefik siehe Traefik als Docker-Reverse-Proxy.
Verifizieren: nginx -t meldet syntax is ok, der Login-Aufruf liefert Set-Cookie mit Secure.
Schritt 9: Sichern und aktualisieren
LibreTV selbst hat keine Daten auf dem Server. Sichern Sie .env, compose.yaml und Caddyfile; das Volume caddy_data enthält die Zertifikate und lässt sich notfalls neu ausstellen. Die Quellen der Nutzer stecken in deren Browsern, dafür ist laut README der Export unter 数据 gedacht.
Behalten Sie PROXY_SECRET bei: Im Test machte ein neuer Wert alle bestehenden Sitzungen ungültig ("verified":false), mit dem alten Wert waren sie wieder gültig. Das ist zugleich der Weg, alle Geräte abzumelden.
Für ein Update setzen Sie den neuen Tag und ziehen das Image. Verfügbare Tags zeigt die Paketseite auf GitHub. Im Test lief der Wechsel von 2.19.2 auf 2.19.5 ohne Zwischenschritte:
sed -i '/^LIBRETV_TAG=/d' .env
echo "LIBRETV_TAG=2.19.5" >> .env
docker compose pull libretv
docker compose up -d libretv
curl -s http://127.0.0.1:8080/api/status | grep -o '"version":"[^"]*"'
Verifizieren: /api/status meldet die neue Version, im Test zuerst 2.19.2, nach dem Wechsel 2.19.5. Bestehende Sitzungen blieben im Test nach dem Neuerstellen des Containers gültig, solange PROXY_SECRET gleich blieb.
Troubleshooting
| Symptom | Ursache | Lösung |
|---|---|---|
| Passwort richtig, Dialog erscheint erneut | Secure-Cookie auf einer HTTP-Seite, etwa COOKIE_SECURE=true ohne HTTPS | Nur über HTTPS aufrufen oder Variable entfernen |
尝试次数过多,请 10 分钟后再试, HTTP 429 | Mehr als zehn Fehlversuche in zehn Minuten von einer IP | Zehn Minuten warten oder Container neu starten (Zähler liegt laut Quellcode im Speicher) |
密码错误, HTTP 401 | Falsches Passwort | Wert in .env prüfen, nach Änderung docker compose up -d |
目标地址解析到私有/保留网络, HTTP 403 | Live-Quelle im internen Netz | LIVE_ALLOW_PRIVATE: "1" setzen |
未登录, HTTP 401 bei API-Aufrufen | Keine gültige Sitzung, etwa nach Wechsel von PROXY_SECRET | Neu anmelden |
Compose startet nicht, required variable PASSWORD is missing | .env fehlt oder liegt im falschen Ordner | docker compose im Projektordner ausführen |
Häufige Fragen
Ist LibreTV legal?
Die Software selbst ja, sie steht unter AGPL-3.0 und enthält keine Inhalte. Die rechtliche Frage stellt sich bei den Quellen: Streaming von Angeboten, die ohne Erlaubnis des Rechteinhabers online stehen, fällt nach dem EuGH-Urteil C-527/15 nicht unter die Ausnahme für vorübergehende Vervielfältigungen. Mit eigenen oder lizenzierten Streams bleiben Sie innerhalb Ihrer Rechte.
Warum klappt der Login über die Server-IP nicht?
Meist trägt das Cookie das Secure-Flag, während die Seite per HTTP läuft; der Browser verwirft es. In 2.19.5 passiert das ohne Proxy nur mit COOKIE_SECURE=true oder einem falsch gesetzten X-Forwarded-Proto. Die richtige Lösung ist ohnehin HTTPS über einen Reverse Proxy.
Brauche ich COOKIE_SECURE hinter Caddy?
Nein. Caddy meldet X-Forwarded-Proto: https, LibreTV setzt dann das Secure-Flag selbst. Die Variable ist für Proxys gedacht, die den Header nicht weitergeben.
Kann LibreTV mehrere Benutzer verwalten?
Nein. Es gibt ein gemeinsames Passwort und keine Konten. Verlauf, Favoriten und Quellen hängen am jeweiligen Browser.
Welche Version läuft gerade?
curl -s https://tv.example.de/api/status zeigt das Feld version, im Fußbereich der Oberfläche steht sie ebenfalls.
Gibt es die Oberfläche auf Deutsch?
In Version 2.19.5 ist die Oberfläche chinesisch. Die wichtigsten Begriffe: 设置 Einstellungen, 源管理 Quellenverwaltung, 直播源 Live-Quellen, 直播 Live, 偏好设置 Vorlieben, 数据 Daten.
Fazit
Technisch ist LibreTV schlank: ein zustandsloser Container ohne Volumes, wenige Variablen, Update per Tag-Wechsel. Wichtig sind drei Punkte: Port 8080 nur lokal binden, HTTPS über einen Reverse Proxy mit korrektem X-Forwarded-Proto und ein fester PROXY_SECRET. Inhaltlich steht und fällt der Nutzen mit den Quellen. Mit eigenen HLS-Streams oder einem lizenzierten IPTV-Zugang funktioniert LibreTV als schlanke Weboberfläche; für unlizenzierte Sammelseiten hilft auch die beste Technik rechtlich nicht weiter.
Weiterführende Anleitungen und Quellen
- Docker und Docker Compose auf Linux installieren
- Caddy als Reverse Proxy mit automatischem HTTPS
- Nginx als Reverse Proxy mit TLS einrichten
- Traefik als Docker-Reverse-Proxy mit HTTPS
- Docker Compose absichern: Secrets, Healthchecks, Non-Root
- LibreTV auf GitHub (README, Umgebungsvariablen)
- LibreTV-Doku: Deployment und Proxy und Sicherheit
- Docker-Doku: Docker Engine auf Ubuntu installieren
- Caddy-Doku: reverse_proxy
- EuGH, Urteil C-527/15 (Filmspeler), § 53 UrhG, § 106 UrhG


