SSD Nodes Learn Hosting plans →
Anleitungen Matt ConnorVon Matt Connor

Paperless-ngx auf einem VPS mit Docker Compose einrichten

Installieren Sie paperless-ngx auf einem VPS mit Docker Compose: offizieller Postgres-Stack, PAPERLESS_URL, Consume-Ordner, OCR-Sprachen, HTTPS und Backups.

Was Sie erstellen

Paperless-ngx verwandelt einen Ordner mit gescannten Dokumenten auf einem VPS in ein durchsuchbares Archiv. Sie legen eine PDF-Datei in einem überwachten Verzeichnis ab. Der Server führt darauf OCR (optische Zeichenerkennung) aus, extrahiert den Text, ermittelt das Datum und den Korrespondenten und legt die Datei ab. Die Installation besteht aus einer Docker-Compose-Datei mit vier Diensten. Danach geht es nur noch um die Konfiguration. Dieser Leitfaden widmet ihr deshalb den größten Teil, weil Installationen an dieser Stelle häufig fehlschlagen. Paperless-ngx ist keine Fotobibliothek: OCR und die Ermittlung von Korrespondenten helfen bei einem Ordner mit Urlaubs-JPEG-Dateien nicht weiter. Legen Sie diese Dateien stattdessen in einem dafür geeigneten Fotoserver ab und verwenden Sie Paperless für Dokumente. Für Videos gilt dieselbe Abgrenzung: Eine Sammlung gerippter Filme gehört auf einen Medienserver. Dort macht beispielsweise eine Jellyfin-Oberfläche im Stil einer Videothek der 90er-Jahre das Stöbern zum eigentlichen Zweck und nicht die Suche.

Paperless-ngx ist der von der Community gepflegte Fork des ursprünglichen Paperless-Projekts. Die Software ist kostenlos, lässt sich selbst hosten und speichert Ihre Dokumente als normale Dateien auf dem Datenträger. Dadurch verlieren Sie nie den Zugriff auf Ihr eigenes Archiv. Wenn Sie die Software auf einem VPS statt auf einem Rechner zu Hause betreiben, sind Ihre Scans von überall erreichbar, ohne einen Port auf Ihrem Heimrouter öffnen zu müssen. Das passt gut zu einer privaten Nextcloud-Instanz für Dateien, die nicht auf Papier vorliegen. Dasselbe gilt für den Desktop, an den Ihr Scanner angeschlossen ist. Mit einem eigenen RustDesk-Relay auf diesem VPS können Sie diesen Rechner von einem anderen Ort aus bedienen, ohne ebenfalls eine Lücke in der Router-Firewall öffnen zu müssen.

Was der Stack tatsächlich ausführt

Die offizielle Compose-Datei startet vier Container. Wenn Sie die Aufgabe jedes Containers kennen, lassen sich die Logs leichter lesen.

  • webserver: das paperless-ngx-Image selbst. Es stellt die Weboberfläche und die API bereit, überwacht den Eingangsordner und führt die Celery-Task-Worker für die OCR-Verarbeitung aus.
  • db: PostgreSQL. Es speichert Metadaten, Tags, Korrespondenten und die Tabellen für den Volltextsuchindex. Ihre PDFs werden dort nicht gespeichert.
  • broker: Valkey, ein Redis-kompatibler Key-Value-Store. Es bildet die Task-Warteschlange zwischen dem Webprozess und den Workern.
  • gotenberg und tika: optional und nur in den -tika-Compose-Varianten enthalten. Sie konvertieren Office-Dokumente (.docx, .xlsx, .odt) in PDF, damit paperless sie indexieren kann.

Seit Oktober 2026 verwendet die PostgreSQL-Compose-Datei die festen Versionen docker.io/library/postgres:18 und docker.io/valkey/valkey:9-alpine und lädt die Anwendung aus ghcr.io/paperless-ngx/paperless-ngx:latest.

Voraussetzungen

  • Ein Ubuntu-24.04-KVM-VPS mit sudo-Zugriff sowie Docker mit bereits installiertem Compose-Plugin. Wenn dieser Teil neu für Sie ist, beginnen Sie mit den Grundlagen zu Docker Compose auf einem VPS und kehren Sie anschließend hierher zurück.
  • Ein Domainname mit einem auf den VPS zeigenden A-Record. Paperless akzeptiert die Anmeldung über HTTPS auf einem Hostnamen nicht, der noch nicht konfiguriert wurde. Das ist daher früher wichtig, als Sie vielleicht erwarten.
  • Der Arbeitsspeicher ist die entscheidende Einschränkung. PostgreSQL, Valkey, der Granian-Webserver und ein Tesseract-OCR-Worker können bei geringer Nutzung gleichzeitig in 2 GB passen. Verwenden Sie 4 GB, wenn Sie einen Rückstand von mehreren hundert Scans importieren möchten. Die OCR-Verarbeitung eines großen mehrseitigen PDF ist der Lastanstieg beim Speicherverbrauch, durch den der Kernel einen Worker wegen Speichermangels beendet.
  • Speicherplatz: Ihr Archiv wird zweimal gespeichert: als Originaldatei und als OCR-Archiv-PDF. Planen Sie daher ungefähr die doppelte Größe Ihrer Scans ein.

Offizielle Compose-Dateien abrufen

Es gibt einen interaktiven Installer:

bash -c "$(curl --location --silent --show-error https://raw.githubusercontent.com/paperless-ngx/paperless-ngx/main/install-paperless-ngx.sh)"

Er stellt Fragen und schreibt die Dateien für Sie. Manuell sind es vier Befehle. Dabei wissen Sie genau, wo alles liegt. Das ist auf einem Server wichtig, den Sie selbst warten.

mkdir -p ~/paperless && cd ~/paperless
curl -fsSL -o docker-compose.yml https://raw.githubusercontent.com/paperless-ngx/paperless-ngx/main/docker/compose/docker-compose.postgres.yml
curl -fsSL -o docker-compose.env https://raw.githubusercontent.com/paperless-ngx/paperless-ngx/main/docker/compose/docker-compose.env
curl -fsSL -o .env https://raw.githubusercontent.com/paperless-ngx/paperless-ngx/main/docker/compose/.env

Die Varianten liegen im selben Verzeichnis: docker-compose.sqlite.yml, docker-compose.mariadb.yml und jeweils eine -tika-Version. Wählen Sie für eine neue Installation postgres. SQLite eignet sich für einige hundert Dokumente. Der Volltextsuchindex wird jedoch deutlich früher langsam als bei PostgreSQL.

Die Datei .env enthält eine Zeile: COMPOSE_PROJECT_NAME=paperless. Dieser Name wird zum Präfix für jeden Container und jedes Volume. Löschen Sie die Datei daher nicht. Andernfalls kann docker compose down -v Ihre Daten nicht mehr finden.

docker-compose.env vor dem ersten Start konfigurieren

Zwei Einstellungen sind zwingend erforderlich. Erzeugen Sie den geheimen Schlüssel mit dem vom Projekt dokumentierten Befehl:

python3 -c "import secrets; print(secrets.token_urlsafe(64))"

Bearbeiten Sie anschließend docker-compose.env:

PAPERLESS_SECRET_KEY=<the long string you just generated>
PAPERLESS_URL=https://paperless.example.com
PAPERLESS_TIME_ZONE=Europe/Berlin
PAPERLESS_OCR_LANGUAGE=deu+eng
USERMAP_UID=1000
USERMAP_GID=1000

PAPERLESS_SECRET_KEY wird standardmäßig mit dem Literalwert change-me ausgeliefert. Der Wert signiert Sitzungstoken. Aktuelle Releases behandeln diesen Platzhalter als nicht gesetzt: paperless verweigert den Start, solange der Schlüssel fehlt, leer ist oder weiterhin change-me lautet. Der Stack wird daher erst gestartet, wenn Sie den Wert ersetzen. Setzen Sie ihn vor dem ersten Start, weil eine spätere Änderung alle Benutzer abmeldet.

PAPERLESS_URL spart Ihnen eine Stunde Fehlersuche. Paperless ist eine Django-Anwendung. Django prüft sowohl den Header Host als auch bei einem Anmeldeformular die vom Browser gemeldete Herkunft. Setzen Sie PAPERLESS_URL. Dadurch fügt die Anwendung Ihre Adresse automatisch zu ALLOWED_HOSTS, CORS_ALLOWED_HOSTS und CSRF_TRUSTED_ORIGINS hinzu. Lassen Sie den Wert leer und verweisen Sie eine Domain auf den Server. Die Anmeldeseite wird dann weiterhin geladen, weil PAPERLESS_ALLOWED_HOSTS standardmäßig auf * gesetzt ist. Das Absenden über HTTPS schlägt jedoch mit CSRF verification failed. Request aborted. fehl, weil Ihre Domain keine vertrauenswürdige Herkunft ist. Geben Sie den Wert ohne abschließenden Schrägstrich und ohne Pfad an.

USERMAP_UID und USERMAP_GID legen den Benutzer fest, unter dem der Container ausgeführt wird. Stimmen Sie die Werte mit Ihrem eigenen Konto ab. Prüfen Sie dieses mit id -u und id -g. Stimmen die Werte nicht überein, kann der Consumer die Dateien, die Sie in den consume-Ordner kopieren, nicht lesen. Im Log erscheint dann ein Berechtigungsfehler statt eines Imports.

Stack starten und den ersten Benutzer anlegen

docker compose pull
docker compose up -d
docker compose run --rm webserver createsuperuser
docker compose logs -f webserver

createsuperuser fordert Sie zur Eingabe eines Benutzernamens, einer E-Mail-Adresse und eines Passworts auf. Es gibt keine Standardanmeldung. Wenn Sie diesen Schritt überspringen, fordert die Weboberfläche die erste Person, die sie öffnet, zur Erstellung des Superusers auf. Solange Port 8000 noch aus dem Internet erreichbar ist, sind Sie das möglicherweise nicht. Legen Sie das Konto daher sofort über die Shell an. Warten Sie, bis die Logzeile meldet, dass der Server auf Port 8000 lauscht, bevor Sie den Browser öffnen. Beim allerersten Start werden außerdem Datenbankmigrationen ausgeführt. Das dauert ein bis zwei Minuten.

Prüfen Sie die Anwendung lokal, bevor Sie eine Domain verwenden:

curl -I http://127.0.0.1:8000

Eine Weiterleitung von 302 zu /accounts/login/ bedeutet, dass der Stack ordnungsgemäß läuft.

HTTPS vorschalten

Die Standard-Compose-Datei veröffentlicht 8000:8000 und bindet den Port damit an alle Schnittstellen. Auf einem öffentlichen VPS stellt das Ihr gesamtes Dokumentarchiv über unverschlüsseltes HTTP für jeden bereit, der die Adresse findet. Ändern Sie die Portzeile so, dass sie nur an Loopback gebunden wird:

    ports:
      - "127.0.0.1:8000:8000"

Beenden Sie TLS (Transport Layer Security) anschließend in einem Reverse Proxy und leiten Sie die Anfragen an 127.0.0.1:8000 weiter. Wenn dies die einzige Anwendung auf dem Server ist, eignet sich jeder Proxy mit einem ACME-Client (Automatic Certificate Management Environment). Wenn mehrere Container hinter einer gemeinsamen Zertifikatskonfiguration laufen, folgen Sie dem Traefik-Reverse-Proxy-Muster für mehrere Docker-Compose-Anwendungen und verbinden Sie den Dienst webserver ohne veröffentlichten Port mit dem Proxy-Netzwerk.

Unabhängig vom verwendeten Proxy ist die Origin-Prüfung des Anmeldeformulars die häufigste Fehlerquelle bei HTTPS-Konfigurationen. Paperless ignoriert X-Forwarded-Proto, sofern Sie PAPERLESS_PROXY_SSL_HEADER nicht setzen. Diese Option ist standardmäßig deaktiviert. Django behandelt deshalb jede per Proxy weitergeleitete Anfrage als unverschlüsseltes HTTP. Die vom Browser gesendete https://-Origin wird nur akzeptiert, wenn sie als vertrauenswürdig eingetragen ist. Mit PAPERLESS_URL und der exakten https://-Adresse, die Sie im Browser eingeben, wird diese Adresse in die Liste aufgenommen. Ohne diese Einstellung erhalten Sie CSRF verification failed. Request aborted. auf einer ansonsten korrekt dargestellten Seite.

Erhöhen Sie außerdem das Upload-Limit des Proxys. Ein 40 MB großer Scan wird von einem Proxy mit einer Begrenzung auf 1 MB abgewiesen, bevor Paperless ihn überhaupt erhält. Der Browser meldet dann lediglich einen allgemeinen Upload-Fehler.

Funktionsweise des Consume-Verzeichnisses

Die Compose-Datei bind-mountet ./consume aus dem Compose-Verzeichnis in den Container. Alles, was Sie dort ablegen, wird importiert und anschließend aus dem Verzeichnis gelöscht, weil die Datei nun im von paperless verwalteten Medien-Volume liegt.

cp ~/scan-2026-07-14.pdf ~/paperless/consume/
docker compose logs -f webserver

Der Consumer sollte den Dateinamen erkennen, die OCR-Verarbeitung ausführen und mit einer Zeile abschließen, die meldet, dass das Dokument hinzugefügt wurde. Bei einem einseitigen Scan dauert der gesamte Vorgang wenige Sekunden. Bei einem langen Dokument kann er eine Minute oder länger dauern.

Zwei Einstellungen ändern, wie Dateien gefunden werden. Mit PAPERLESS_CONSUMER_RECURSIVE=true sucht paperless auch in Unterverzeichnissen. PAPERLESS_CONSUMER_SUBDIRS_AS_TAGS=true wandelt jeden Namen eines Unterverzeichnisses in ein Tag um. Wenn Sie eine Datei in consume/invoices/2026/ ablegen, erhält sie die Tags invoices und 2026. Einfacher lässt sich kein Ablagesystem einrichten.

Die Erkennung ist die andere Hälfte. Standardmäßig ist PAPERLESS_CONSUMER_POLLING_INTERVAL auf 0 gesetzt. Das bedeutet, dass paperless native Dateisystembenachrichtigungen verwendet, die sofort ausgelöst werden. Diese Benachrichtigungen sind bei einem Netzwerkdateisystem nicht zuverlässig. Wenn Ihr Consume-Verzeichnis eine NFS- oder SMB-Freigabe ist, in die ein Netzwerkscanner Dateien schreiben kann, bleiben Dateien möglicherweise unerkannt dort liegen. Setzen Sie in diesem Fall das Intervall auf eine positive Anzahl von Sekunden, damit paperless das Verzeichnis stattdessen regelmäßig scannt.

OCR-Sprachen und ihre Kosten

PAPERLESS_OCR_LANGUAGE erwartet einen dreistelligen Tesseract-Code, standardmäßig eng. Kombinieren Sie Sprachen mit einem Pluszeichen, zum Beispiel deu+eng. Tesseract verarbeitet dann jede aufgeführte Sprache auf jeder Seite. Jede zusätzliche Sprache erhöht daher die CPU-Zeit für jeden Scan. Auf einer VPS mit gemeinsam genutzten vCPUs kann das den Unterschied zwischen einem Scan ausmachen, der nach zehn Sekunden fertig ist, und einem Scan, der eine Minute benötigt. Geben Sie nur die Sprachen an, in denen Ihre Dokumente tatsächlich verfasst sind.

Das Image enthält Englisch, Deutsch, Italienisch, Spanisch und Französisch. Für jede weitere Sprache fügen Sie die Sprache als durch Leerzeichen getrennte Liste zu PAPERLESS_OCR_LANGUAGES hinzu, zum Beispiel PAPERLESS_OCR_LANGUAGES=tur ces, und starten Sie den Container neu. Der Container lädt die Tesseract-Datenpakete beim Start herunter. Der erste Start nach dieser Änderung dauert daher länger.

Datenbank und Mediendateien sichern

Wenn Sie die Docker-Volumes kopieren, während PostgreSQL läuft, kann die Sicherung möglicherweise nicht wiederhergestellt werden. Paperless enthält einen eigenen Exporter. Er schreibt die Dokumente sowie ein JSON-Manifest mit allen Metadaten in das ./export-Bind-Mount:

docker compose exec -T webserver document_exporter ../export --delete --no-progress-bar

--delete entfernt exportierte Dateien, die keinem aktuellen Dokument mehr entsprechen. Dadurch bleibt der Ordner ein Abbild, statt unbegrenzt zu wachsen. --no-progress-bar hält die Ausgabe sauber, wenn der Export über cron ausgeführt wird. -T verhindert, dass Compose ein Terminal anfordert, das cron nicht besitzt.

Die Wiederherstellung erfolgt document_importer aus demselben Ordner in einem neuen Stack. Damit ist nur das Exportverzeichnis zu sichern. Übertragen Sie es regelmäßig an einen anderen Standort, zum Beispiel mit verschlüsselten, deduplizierten restic-Sicherungen von Ihrem VPS. Führen Sie den Export zuerst aus. So erfasst restic niemals ein unvollständig geschriebenes Archiv.

Überprüfen Sie eine Sicherung, indem Sie kontrollieren, ob export/manifest.json vorhanden ist und die Dateianzahl mit der Dokumentanzahl in der Benutzeroberfläche übereinstimmt. Eine Sicherung, deren Inhalt Sie nie aufgelistet haben, ist keine Sicherung. Noch problematischer ist ein nächtlicher Export, der unbemerkt fehlschlägt. Lassen Sie den cron-Job daher seinen Exit-Status an Ihren eigenen ntfy-Server senden. So erfahren Sie in der Woche, in der der Fehler auftritt, davon und nicht erst an dem Tag, an dem Sie die Wiederherstellung benötigen.

FAQ

Warum gibt jede Seite „Bad Request (400)“ zurück, nachdem ich meine Domain darauf verweise?

Mit den mitgelieferten Dateien sollte das nicht passieren, weil PAPERLESS_ALLOWED_HOSTS standardmäßig auf * gesetzt ist. Ein 400 mit DisallowedHost im Container-Log bedeutet, dass PAPERLESS_ALLOWED_HOSTS auf eine Liste gesetzt wurde, in der Ihre Domain fehlt. Setzen Sie PAPERLESS_URL=https://paperless.example.com in docker-compose.env, ohne abschließenden Schrägstrich. Paperless fügt diesen Hostnamen dann der Liste hinzu. Alternativ entfernen Sie die Zeile PAPERLESS_ALLOWED_HOSTS. Führen Sie anschließend docker compose up -d aus, um den Container neu zu erstellen. Das alleinige Bearbeiten der Env-Datei bewirkt nichts, weil der laufende Container die Umgebung verwendet, mit der er gestartet wurde.

Ich habe eine PDF-Datei im Consume-Ordner abgelegt, aber es ist nichts passiert. Was ist falsch?

Prüfen Sie zuerst docker compose logs webserver. Ein Berechtigungsfehler bedeutet, dass USERMAP_UID und USERMAP_GID nicht mit dem Konto übereinstimmen, dem die Datei gehört. Korrigieren Sie die Berechtigungen und erstellen Sie den Container neu. Wenn überhaupt keine Log-Zeile erscheint, ist das Datei-Ereignis nicht angekommen. Das passiert bei Netzwerkfreigaben, weil native Dateisystembenachrichtigungen dort nicht zuverlässig funktionieren. Setzen Sie PAPERLESS_CONSUMER_POLLING_INTERVAL auf einen Wert wie 30. Paperless überprüft den Ordner dann stattdessen alle 30 Sekunden.

Kann ich paperless-ngx anstelle von PostgreSQL mit SQLite betreiben?

Ja, docker-compose.sqlite.yml wird unterstützt und benötigt weniger Arbeitsspeicher. Das eignet sich für einen kleinen VPS. Der Nachteil zeigt sich, wenn Ihr Archiv wächst: Die Volltextsuche und die Bearbeitung von Tags für viele Dokumente werden bei mehreren tausend Dokumenten deutlich langsamer. Eine spätere Migration erfordert einen Export und einen Import. Verwenden Sie daher jetzt PostgreSQL, wenn Ihr Archiv voraussichtlich weiter wächst.

Wie viel Speicherplatz benötigt ein Archiv mit Scans tatsächlich?

Rechnen Sie ungefähr mit dem Zweifachen der Größe Ihrer Quelldateien. Paperless bewahrt das Original unverändert auf und speichert zusätzlich eine zweite PDF-Datei mit OCR und durchsuchbarer Textebene sowie kleine Vorschaubilder. Ein textbasierter Scan mit 200 KB bleibt klein. Ein 30 MB großer Farbscan eines langen Vertrags benötigt etwa 60 MB. Wenn Sie das Exportverzeichnis auf demselben Datenträger speichern, müssen Sie es zusätzlich berücksichtigen. Das gleiche Archiv belegt dann dreimal so viel Speicherplatz.

Benötige ich die Container Tika und Gotenberg?

Nur wenn Sie Word-, Excel- oder OpenDocument-Dateien zusammen mit Ihren PDFs indizieren möchten. Die Container konvertieren diese Formate in PDF, damit Paperless sie per OCR verarbeiten und durchsuchen kann. Sie fügen jedoch zwei weitere laufende Container und einige hundert Megabytes Arbeitsspeicher hinzu. Lassen Sie sie auf einem kleinen System weg, wenn alle von Ihnen archivierten Dateien bereits PDFs oder Bilder sind.

#paperless-ngx#documents#self-hosting#docker#ocr