PrivateBin selbst hosten: Dein Zero-Knowledge-Pastebin mit Docker Compose
Teile Geheimnisse, Logs oder SSH-Keys, ohne dass der Server sie je zu Gesicht bekommt:
PrivateBin verschlüsselt jeden Paste direkt im Browser (AES-256-GCM) – auf dem Server liegt nur Chiffrat,
der Schlüssel verlässt dein Gerät nie. In diesem Tutorial richtest du dir dein eigenes Zero-Knowledge-Pastebin
mit Docker Compose ein – inklusive korrekter Volume-Rechte, read_only-Betrieb und Reverse Proxy.
Warum ein eigenes Pastebin?
Öffentliche Paste-Dienste wirken praktisch – bis du Log-Auszüge, Konfigurationen oder Tokens einfügst, die dir nicht gehören. Wer selbst hostet, entscheidet, was gespeichert wird, wie lange es lebt und wer darauf zugreifen darf. Und weil PrivateBin zusätzlich Ende-zu-Ende-verschlüsselt, wird aus „eigener Server“ sogar „der Server sieht nichts“.
Konzept: Zero Knowledge & XSS-Schutz
PrivateBin (Fork des eingestellten ZeroBin) funktioniert anders als ein klassisches Pastebin:
- Verschlüsselung im Browser: AES-256 im Galois-Counter-Modus; der Schlüssel steht nur in der URL nach dem
#und wird nie zum Server gesendet. - Server ohne Wissen: Gespeichert wird ausschließlich Chiffrat – selbst ein Server-Admin kann Inhalte nicht lesen.
- XSS-Schutz: Strikte Content-Security-Policy, kein aktiver Inhalt von Dritten, Paste-Inhalte werden nie roh gerendert.
- Kontrolle über Lebensdauer: Ablaufzeit, Passwortschutz und „Burn after reading“ (Paste löscht sich nach dem ersten Öffnen).
Vorbereitung: Ordner & Volume-Rechte
Das Image ist ein All-in-One-Container (Nginx + PHP-FPM + Alpine), dessen Prozesse als UID 65534 / GID 82 („nobody“) laufen. Genau diese Rechte braucht deshalb auch dein Daten-Volume – sonst kann PrivateBin keine Pastes speichern:
mkdir -p ~/privatebin/privatebin-data
sudo chown -R 65534:82 ~/privatebin/privatebin-data
user: 1000:1000 setzen! Der offizielle Container startet seine Dienste über s6 mit festen Benutzern.
Ein Container-User von außen bricht diese Annahme und führt zu Schreibfehlern oder 502/403. Stattdessen gehört das Volume dem
Nutzer 65534:82 – genau das erledigt der chown oben.
Docker Compose – die Datei
So sieht das fertige Setup aus (deine Vorlage, aufgeräumt und um die offiziell empfohlenen tmpfs-Mounts ergänzt):
services:
privatebin:
image: privatebin/nginx-fpm-alpine:stable # statt Tag-Los: stable/Version pinnen
container_name: privatebin
restart: unless-stopped
read_only: true
tmpfs:
- /tmp:nodev,noexec,mode=1777
- /run:nodev,exec,mode=1777
deploy:
resources:
limits:
cpus: "2.0"
memory: 2G
ports:
- "127.0.0.1:50520:8080" # Web-UI nur lokal
volumes:
- ./privatebin-data:/srv/data # Pastes, Salt & Limiter-Daten
# - ./conf.php:/srv/cfg/conf.php:ro # nur bei eigener conf.php (siehe unten)
Warum die einzelnen Zeilen?
read_only: true+tmpfs: Das Root-Dateisystem ist schreibgeschützt; beschreibbar bleiben nur/tmp,/run,/var/lib/nginx/tmpund/srv/data. Das verkleinert die Angriffsfläche deutlich – wird vom Image offiziell unterstützt.127.0.0.1:50520:8080: Der Container lauscht intern auf Port 8080 (Nginx). Gebunden wird nur an localhost – der Zugriff von außen läuft ausschließlich über deinen Reverse Proxy../privatebin-data:/srv/data: Hier landen Pastes, der Server-Salt und Limiter-Daten – das ist dein komplettes Backup.- Das Image kennt nur HTTP – TLS übernimmt zwingend der Proxy davor.
Konfiguration (conf.php) & Umgebungsvariablen
Standardmäßig startet der Container mit einer sicheren Default-Konfiguration. Willst du z. B. Datei-Uploads,
Diskussionen, ein eigenes Template oder andere Speicher-Backends aktivieren, legst du eine eigene
conf.php an und mountest sie schreibgeschützt nach /srv/cfg/conf.php:
# Beispiel: conf.php in ~/privatebin ablegen und zusätzlich mounten
# volumes:
# - ./conf.php:/srv/cfg/conf.php:ro
- Basis ist die
conf.sample.phpdes Projekts – einfach kopieren, anpassen, mounten. - Alternativ lässt sich der Konfigurationspfad über die Variable
CONFIG_PATHverschieben. - Zeitzone für Logs und Ablaufzeiten:
TZundPHP_TZsetzen (Achtung: spätere Änderung verschiebt Ablaufzeiten bestehender Pastes!). - Standard-Limit für Datei-Uploads sind 10 MiB – mehr geht über eigene
php.ini- (nach/etc/php/conf.d/) und Nginx-Dateien (nach/etc/nginx/http.d/, Stichwortclient_max_body_size).
Reverse Proxy & HTTPS
Der Container spricht nur HTTP – also gehört ein TLS-terminierender Proxy davor. Da der Port nur auf localhost lauscht, reicht ein Proxy auf demselben Host:
paste.meine.domain {
encode zstd gzip
reverse_proxy 127.0.0.1:50520
}
#…). Über unverschlüsseltes HTTP wäre er im Klartext unterwegs – und damit wäre die Browser-Verschlüsselung wertlos.
Erster Start & Nutzung
cd ~/privatebin
docker compose up -d
docker compose logs -f privatebin # bis "ready" / ohne Fehler
Danach https://paste.meine.domain öffnen – es gibt kein Admin-Konto, die Seite ist sofort nutzbar:
- Neuer Paste: Text einfügen, Ablaufzeit wählen, optional Passwort setzen oder „Nach dem Lesen löschen“ aktivieren → Link teilen.
- QR-Code & Syntax-Highlighting inklusive – praktisch fürs Übertragen aufs Handy.
- Standardmäßig sind Datei-Upload und Kommentarfunktion deaktiviert – wer sie braucht, aktiviert sie in der
conf.php.
Wartung, Purge & Backups
Das Image bringt zwei Admin-Skripte mit, die du direkt im laufenden Container aufrufst:
# abgelaufene Pastes aufräumen
docker exec -t privatebin administration --purge
# Statistik über gespeicherte Pastes
docker exec -t privatebin administration --statistics
Für Updates reicht docker compose pull && docker compose up -d – mit getaggten Images
(z. B. :stable oder :1.5.1) behältst du die Kontrolle, wann Neues kommt.
:latest folgt dagegen der neuesten Veröffentlichung.
./privatebin-data enthält neben den Pastes auch den Server-Salt.
Geht er verloren, lassen sich alte Pastes nicht mehr entschlüsseln. Ein tar des Ordners (bei laufendem Stack: vorher kurz stoppen) genügt als vollständiges Backup.
Häufige Probleme (FAQ)
| Problem | Lösung |
|---|---|
| Pastes lassen sich nicht speichern / 500er | Volume-Rechte prüfen: sudo chown -R 65534:82 privatebin-data und Container neu starten. |
| Eigene conf.php wird ignoriert | Muss exakt nach /srv/cfg/conf.php gemountet sein – Pfad und Dateiname prüfen, Container neu erstellen (up -d --force-recreate). |
| Fehler beim Schreiben trotz Volume | Bei read_only: true die tmpfs-Einträge für /tmp und /run ergänzen – ohne sie kann PHP-FPM keine Sockets/Caches anlegen. |
| Datei-Uploads über 10 MiB schlagen fehl | client_max_body_size (Nginx) und upload_max_filesize/post_max_size (PHP) erhöhen – Dateien in /etc/nginx/http.d/ und /etc/php/conf.d/ mounten. |
| Ablaufzeiten verschieben sich nach TZ-Änderung | Ablaufzeiten werden mit der PHP_TZ zum Erstellen berechnet – Zeitzone möglichst von Anfang an setzen. |
| 502/403 hinter dem Proxy | Container läuft nicht als user: 1000; Rechte-Hinweis oben befolgen und Logs via docker compose logs privatebin prüfen. |
Fazit
PrivateBin ist in zehn Minuten aufgesetzt und danach erstaunlich unauffällig im Betrieb: kein Konto,
keine Datenbank, ein kleiner Datenordner. Der Clou bleibt das Konzept – selbst dein eigener Server
kann deine Pastes nicht lesen. Kombiniert mit read_only, HTTPS davor und regelmäßigem
Backup ist das Pastebin so privat, wie Pastebins nur sein können.
user: 1000. ②
read_only + tmpfs für /tmp & /run. ③ Immer HTTPS davor,
der Schlüssel lebt im URL-Fragment. ④ Backup von privatebin-data inkl. Server-Salt.