Was tun, wenn ich keine Server zuhause betreiben aber trotzdem Paperless-NGX nutzen möchte? Nun, es gibt Anbieter, die Paperless NGX hosten und das als Dienstleistung anbieten. Ich möchte aber eine Paperless-NGX-Installation bei einem Hoster aufsetzen und sie ansonsten selbst betreiben. Dabei sind folgende Merkmale gewünscht:
Folgende Funktionalitäten sind gewünscht:

Die Lösung besteht aus einem Server, der bei einem Hoster angemietet wird. Auf diesem läuft zunächst Wireguard, um eine VPN-Verbindung zu Clients im lokalen Netzwerk aufzubauen. Das wird ausschließlich für die Administration des Systems benötigt. Normale Userzugriffe auf Paperless-NGX werden später über die öffentlich zugängliche Domain stattfinden. Dann läuft auf dem Server unter Docker der Nginx Proxy Manager (NPM), Paperless-NGX und die Datensicherungslösung Duplicati. Der NPM und Paperless-NGX laufen im selben Docker-Netzwerk. Das ist ein Sicherheitsdetail, auf das ich später noch eingehen werde. Zum Schutz vor unbefugten Zugriffen und Manipulationen kommen restriktive Firewalleinstellungen zum Einsatz und mit fail2ban wird ein Schutz vor Brute Force Attacken realisiert. Die Datensicherung wird später auf einen Cloud-Speicher durchgeführt; natürlich verschlüsselt.
Man nehme:
Ich werde hier keine Empfehlungen für oder gegen das eine oder andere Hosting-Angebot aussprechen. Die Angebote der verschiedenen Hoster unterscheiden sich hinsichtlich der Speichermengen, der Systemressourcen, der Bandbreite, mit der die Server ans Internet angebunden sind und auch hinsichtlich der jeweils angebotenen Services (mit oder ohne Firewall vor dem Server, mit oder ohne Snapshots, Nutzung eigener Bootmedien oder ausschließlich vom Anbieter bereit gestellter Medien...). Hier muss man sich überlegen, was man braucht und möchte. Meine Paperless-NGX-Installationen kommen in der Regel mit 2 Prozessorkernen und 4 GB RAM aus. Beim Massenspeicher muss man überlegen, wie viel man vielleicht braucht. Wenn man davon ausgeht, am Anfang nicht sehr viel zu brauchen, dann mögen vielleicht 32 GB Massenspeicher ausreichen. Wenn der aber irgendwann knapp wird, dann ist ein Umzug auf einen besser ausgestatteten Server zumindest nicht unaufwändig. Für den Anfang sollte aber eine Kapazität von etwa 80 GB reichen.
Für diese recht geringen Anforderungen werden von den meisten Hostern (z.B. Hetzner, Ionos, Netcup oder Strato) Produkte in der Preisklasse um die 4 - 5 € pro Monat angeboten. Für die Datensicherung empfehle ich einen Cloudspeicher bei einem anderen Anbieter, als dem bei dem der Server gemietet wird. Da die Sicherungen verschlüsselt übertragen und gespeichert werden, ist das aus Sicherheits- und Datenschutzgesichtspunkten her unbedenklich. Solche Speicherlösungen werden für 3 - 5 € im Monat angeboten (z.B. die Hetzner Storage-Box für 3,20 € für 1 TB oder das Strato HiDrive Start für 3,5 € für 500 GB). Somit summieren sich die monatlichen laufenden Kosten für die vorgesehene Lösung auf 7 - 10 € im Monat.
Die Basisinstallation des Servers beschreibe ich hier nicht, weil sich das zum Einen von Hoster zu Hoster geringfügig unterscheidet und zum anderen recht selbst erklärend ist. Die folgenden Schritte basieren auf einem Ubuntu Linux Server oder einem Debian, jeweils in aktueller Long Term Service Version. Ich beschreibe hier die Absicherung des Servers. Zur Basisabsicherung des Servers s. hier: https://tutorials.kernke.koeln/sicherheit/server-absichern.html. Wer seinem Provider seine Daten nicht anvertrauen möchte, kann zusätzlich eine verschlüsselte Partition einrichten. Darauf verzichte ich hier, weil ich Angebote von zertifizierten Providern in Deutschland nutze. Wenn man Bootmedien bzw. entsprechende Templates von Providern für das Betriebssystem des Server nutzt dann, wird bei der Basisinstallation oftmals nur ein Root-User angelegt und kein normaler User ohne Administrationsprovilegien. Die folgenden Anweisungen gehen jeweils davon aus, dass ein normaler User genutzt wird. Wenn der noch nicht existiert, muss er zunächst angelegt werden. Das macht man mit dem Befehl:
adduser --shell /bin/bash {Benutzername}
In den darauf folgenden Dialogen wird man aufgefordert, ein Passwort für den User zu vergeben. Ggf. muss man noch das Tool sudo installieren, mit dem man dem normalen User das Recht einräumen kann, bedarfsweise für einzelne Anweisungen Root-Rechte zu nutzen
apt install sudo
und den Benutzer der Gruppe sudo hinzufügen, damit er das auch darf
usermod -aG sudo {Benutzername}
Danach kann man sich ab- und mit dem neuen Useraccount wieder anmelden.
Zunächst installiere ich fail2ban und schütze das System auf diese Weise vor Brute Force Attacken gegen die SSH-Schnittstelle. Die Installation geht einfach mit dem Befehl
sudo apt install fail2ban
Damit ist bereits die Absicherung der SSH-Schnittstelle standardmäßig aktiv. Hier muss zunächst nichts weiter gemacht werden. Später werde ich fail2ban noch auf die Anmeldung an der Weboberfläche von Paperless-NGX ansetzen.
Wenn der Hoster eine Firewall anbietet, die bereits zwischen dem Server und dem Internet arbeitet, dann sollte diese auf jeden Fall genutzt werden. Alles, was abgefangen wird, bevor es überhaupt den Server erreicht, belastet natürlich nicht die Systemressourcen unseres Servers. Aber auch wenn es eine solche Firewall des Hosters gibt, sollte noch eine Firewall auf dem Server eingesetzt werden. IT-Sicherheit basiert immer auf dem Prinzip der möglichst vielen Schichten. Ich verwende die ufw, die uncomplicated firewall. Soweit sie noch nicht auf dem Server installiert ist, hole ich das mit
sudo apt install ufw
nach. Dann schließe ich zunächst alle eingehenden Verbindungen aus
sudo ufw default deny incoming
und erlaube dann die Verbindungen, die ich unbedingt brauche
sudo ufw allow 51820
sudo ufw allow 22
sudo ufw allow 80
sudo ufw allow 443
sudo ufw allow from 10.11.0.0/24 to any port 22 proto tcp
sudo ufw allow from 10.11.0.0/24 to any port 81 proto tcp
sudo ufw allow from 10.11.0.0/24 to any port 8200 proto tcp
sudo ufw allow from 10.11.0.0/24 to any port 445 proto tcp
Hier nehme ich bereits die Adressen des VPN vorweg. Dieses werde ich für den Adressbereich 10.11.0.1 bis 10.11.0.255 einrichten. Um das VPN überhaupt etablieren zu können, wird ein offener Port 51820 benötigt. Zunächst brauche ich auch einen offenen Port 22. Später soll nur aus dem VPN heraus soll auf die SSH-Shell (Port 22) zugegriffen werden können. Dann schließe ich die allgemeine Freigabe von Port 22 wieder. Ebenso soll auch auf die Weboberfläche des NPM (Port 81) und die Weboberfläche von Duplicati (Port 8200) nur aus dem VPN heraus zugegriffen werden können. Außerdem soll das Consume-Verzeichnis von Paperless NGX später per Samba für das VPN freigegeben werden. Dafür wird Port 445 benötigt. Diese Einstellungen aktiviere ich mit
sudo ufw enable
und nach einer Sicherheitsabfage sind die Firewalleinstellungen aktiv. Wenn ich mich jetzt nicht ausgesperrt habe, dann bleibt die Verbindung bestehen und ich kann weiter arbeiten.
Wireguard ist eine leichtgewichtige und effiziente VPN-Lösung. Ein VPN, also ein virtuelles privates Netzwerk stellt eine Möglichkeit dar, innerhalb eines öffentlichen Netzwerks oder sogar über Netzwerkgrenzen hinweg mittels Verschlüsselungstechnologie ein abgeschottetes Netzwerk zu etablieren. Das ist ein starkes Sicherheitsfeature, um Zugriffe auf sensible Dienste und Daten abzusichern. Zunächst installiere ich wireguard mit
sudo apt install wireguard
Dann wechsele ich in den Root-Modus, weil ich in den geschützten Ordner /etc/wireguard wechseln und außerdem mehrere Befehle mit Adminrechten ausführen muss.
sudo su
und wechsele in den Ordner /etc/wireguard
cd /etc/wireguard
Zunächst erstelle ich ein Schlüsselpaar für die Nutzung von Wireguard
umask 077; wg genkey | tee privatekey | wg pubkey > publickey
Die Schlüssel lasse ich mir anzeigen, um sie über die Zwischenablage wegzusichern:
cat privatekey
cat publickey
Dann schränke ich die Zugriffsrechte auf den privaten Schlüssel auf den Besitzer der Datei ein, sonst arbeitet Wireguard nicht damit:
chmod 600 privatekey
Nun erstelle ich eine Datei zur Konfiguration des VPN-Tunnels. Ich nenne sie wg1011.conf, weil ich das Subnetz 10.11.0.0/24 nutzen möchte.
nano wg1011.conf
mit folgendem Inhalt:
[Interface] Address = 10.11.0.1/32 SaveConfig = true PostUp = iptables -A FORWARD -i wg1011 -j ACCEPT; iptables -t nat -A POSTROUTING -o ens18 -j MASQUERADE PostDown = iptables -D FORWARD -i wg1011 -j ACCEPT; iptables -t nat -D POSTROUTING -o ens18 -j MASQUERADE ListenPort = 51820 PrivateKey = Private Key des Servers
Der Server wird hier als VPN-Router fungieren und erhält die VPN-Adresse 10.11.0.1. Die Parameter PostUp und PostDown definieren, was beim Start des VPN-Tunnels und bei dessen Beendigung passieren soll. Es wird ein internes Nating, also ein virtuelles Netzwerk generiert. Hier muss die richtige Bezeichnung der Netzwerkschnittstelle eingetragen werden. Bei meinem Beispiel ist es "ens18". Die individuell richtige kann man mit dem Befehl
ip a
ermitteln. Es ist in der Regel die zweite Schnittstelle nach "lo", der Loopback-Schnittstelle. Den ListenPort kann man ändern, wenn man das möchte. Hier nehme ich 51820, was Standard ist für Wireguard. Dass ich unter PrivateKey den soeben kopierten privaten Schlüssel eintrage, ist klar. Nun kann ich mit
wg-quick up wg1011
den Tunnel starten und mit
systemctl enable wg-quick@wg1011
dafür sorgen, dass er bei künftigen Neustarts des Servers direkt mit gestartet wird.
Zur Einrichtung des Wireguard-Tunnels auf dem Client installiere ich auch dort, wenn nicht bereits geschehen Wireguard mit
sudo apt install wireguard
Ich wechsele in den Root-Modus mit
sudo su
und in den Wireguard-Ordner mit
cd /etc/wireguard
Dann erstelle ich ein Schlüsselpaar mit
umask 077; wg genkey | tee privatekey | wg pubkey > publickey cat privatekey cat publickey
dann erstelle ich die Konfigurationsdatei für den VPN-Tunnel mit
sudo nano /etc/wireguard/wg1011.conf
mit folgendem Inhalt:
[Interface]
PrivateKey = {privater Schlüssel des Clients}
Address = 10.11.0.36/32
[Peer]
PublicKey = {öffentlicher Schlüssel des Servers}
Endpoint = {IP-Adresse des Servers}:51820
AllowedIPs = 10.11.0.0/24
PersistentKeepalive = 25
Den neue Tunnel aktiviere ich mit
wg-quick up wg1011
und mache ihn bootfest mit
systemctl enable wg-quick@wg1011
Danach verlasse ich den Root-Modus wieder mit
exit
Nun muss ich auf dem Server noch den Client in das VPN aufnehmen. Ich melde mich also remote auf dem Server an und setze den Befehl
sudo wg set wg1011 peer {öffentlicher Schlüssel des Clients} allowed-ips 10.11.0.36/32
ab. Damit das auch wirksam wird, muss ich den VPN-Tunnel auf dem Server zunächst beenden und wieder starten mit
sudo wg-quick down wg1011 && sudo wg-quick up wg1011
Das teste ich mit
ping 10.11.0.36
auf dem Server und/oder mit
ping 10.11.0.1
auf dem Client. Beides sollte funktionieren, wenn ich keinen Fehler gemacht habe. Wenn das nicht klappt, muss ich die Konfigurationsdateien prüfen. Ein häufiger Fehler besteht darin, privaten und öffentlichen Schlüssel zu verwechseln. Wenn alles klappt, dann kann ich den Port 22 für die große weite Welt schließen, so dass er künftig nur noch aus dem VPN heraus genutzt werden kann. Ich schaue mir die aktuellen Firewalleinstellungen mit
sudo ufw status numbered
an und identifiziere die Freigabe für Port 22 für alle. Das ist in meinem Fall die Nummer 2. Ich lösche sie mit
sudo ufw delete 2
und bestätige die Sicherheitsabfrage. Um das wirksam zu machen, muss ich den Befehl
sudo ufw enable
absetzen, weil ein Reload nicht möglich ist. Jetzt fliege ich vermutlich aus der SSH-Session heraus, weil sie über den weltweit offenen Port 22 aufgebaut wurde. Mit
ssh -l andreas 10.11.0.1
kann ich mich aber sofort wieder anmelden.
Da die mit den gängigen Linux-Distributionen mitgelieferten Docker-Implementierungen nicht immer ganz kompatibel mit den auf Github bereitgestellten Containern ist, installiere ich Docker und Docker Compose aus den Originalquellen. Dazu habe ich mir ein kleines Script geschrieben:
# Shell-Skript zur Installation von Docker (mit sudo ausführen:
#!/bin/bash
for pkg in docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; do
apt-get remove $pkg;
done
apt-get update
apt-get install ca-certificates curl
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
chmod a+r /etc/apt/keyrings/docker.asc
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
tee /etc/apt/sources.list.d/docker.list > /dev/null
apt-get update
apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
systemctl start docker
systemctl enable docker
usermod -aG docker "$SUDO_USER"
Wichtig: Wenn man keinen Ubuntu-Server nutzt, sondern ein Debian, dann muss man in dem Script die Zeichenkette ubuntu gegen debian austauschen. Dieses Script muss zunächst gespeichert werden, z.B. unter dem Namen installdocker.sh. Dann muss es ausführbar gemacht werden mit
chmod +x installdocker.sh
Danach muss es mit Rootrechten ausgeführt werden:
sudo ./installdocker.sh
Das Script deinstalliert zunächst ggf. vorhandene Bibliotheken, die Probleme machen könnten und installiert dann die benötigten Pakete. Außerdem fügt es den aufrufenden Benutzer (was trotz "sudo" nicht root ist) der Gruppe docker hinzu, damit ich nachher ohne sudo auch docker-Befehle ausführen kann. Nun muss man sich einmal kurz ab- und wieder anmelden, damit die neue Dockerberechtigung auch greift. Ob das alles funktioniert hat prüfe ich mit dem Befehl:
docker ps
Damit würden laufende Docker-Container aufgelistet. Hier laufen zwar noch keine, aber wenn ich hierauf keine Fehlermeldung erhalte, sondern die Überschriften einer leeren Tabelle, dann ist alles richtig.
Der NPM wird ganz einfach mit docker und docker-compose installiert. Ich erstelle zunächst einen Ordner npm und wechsele in selbigen
mkdir npm
cd npm
dann erstelle ich darin erst eine Konfigurationsdatei namens
config.json
nano config.json
mit folgendem Inhalt:
{
"database": {
"engine": "mysql",
"host": "db",
"name": "npm",
"user": "npm",
"password": "npm",
"port": 3306
}
}
und eine Datei docker-compose.yml
nano docker-compose.yml
mit folgendem Inhalt:
services:
app:
image: jc21/nginx-proxy-manager:latest
restart: always
ports:
- 80:80
- 10.11.0.1:81:81
- 443:443
volumes:
- ./config.json:/app/config/production.json
- ./data:/data
- ./letsencrypt:/etc/letsencrypt
networks:
- web_proxy
depends_on:
- db
environment:
# if you want pretty colors in your docker logs:
- FORCE_COLOR=1
db:
image: mariadb:latest
restart: always
environment:
MYSQL_ROOT_PASSWORD: "npm"
MYSQL_DATABASE: "npm"
MYSQL_USER: "npm"
MYSQL_PASSWORD: "npm"
volumes:
- ./data/mysql:/var/lib/mysql
networks:
- web_proxy
networks:
web_proxy:
external: true
Hier sind folgende Besonderheiten zu beachten: Port 81, der für die Weboberfläche des NPM genutzt wird, wird an die IP-Adresse aus dem VPN gebunden. Außerdem werden die Services des NPM in das Netzwerk web_proxy verlegt. In diesem Netzwerk soll später auch der Paperless-Webserver, also die Weboberfläche von Paperless-NGX laufen. So kann dann der Webserver im NPM mit dem Namen des Containers (also webserver) angesprochen werden. Dieses Docker-Netzwerk muss ich mit dem (naheliegenden) Befehl
docker network create web_proxy
erstellen. Dann kann ich den NPM starten mit
docker compose up -d
Wer Docker nicht mit dem oben aufgeführten Script oder sonst wie aus den Originalquellen installiert hat, sondern zum Beispiel aus den Repositories der Linux-Distribution, muss ggf. den Befehl docker-compose (also mit Bindestrich) verwenden. Nun sollte der NPM laufen. Das kann man sich mit
docker ps
ansehen. Hier sollten nun die Container "npm-app-1" und "npm-db-1" mit dem Status "up" zu sehen sein. Dann hat alles geklappt.
Auch Paperless-NGX wird mit docker und docker-compose aufgesetzt. Zunächst wechsele ich aus dem Verzeichnis npm wieder zurück in mein Homeshare
cd ..
und erstelle ein Verzeichnis paperless-ngx, in das ich dann auch wechsele
mkdir paperless-ngxcd paperless-ngx
Darin erstelle ich zunächst eine Datei docker-compose.env
nano docker-compose.env
mit folgendem Inhalt:
PAPERLESS_URL=https://paperless.domain.de
PAPERLESS_TIME_ZONE=Europe/Berlin
PAPERLESS_OCR_LANGUAGE=deu+eng
PAPERLESS_SECRET_KEY='V<_dddxkOW[.l,VF{:%cQAT|$uAIZY)n;MWH/LU?MJ_hBV7HGb%7KhY)+dSethRvWSV'
Dabei ist beim URL idie Domain einzutragen, unter der Paperless-NGX später erreichbar sein soll und für die ich auf meinen Server einen A-Record eingetragen habe. Für den Secret-Key kann eine beliebige, aber möglichst lange Zeichenkette genutzt werden. Die anderen Parameter sind wohl selbst erklärend. Nun brauche ich natürlich noch eine Datei docker-compose.yml
nano docker-compose.yml
mit folgendem Inhalt:
# Docker Compose file for running paperless from the docker container registry.
# This file contains everything paperless needs to run.
# For more extensive installation and update instructions, refer to the
# documentation.
services:
broker:
image: docker.io/library/redis:8
restart: unless-stopped
networks:
- web_proxy
volumes:
- redisdata:/data
db:
image: docker.io/library/postgres:17
restart: unless-stopped
volumes:
- pgdata:/var/lib/postgresql/data
networks:
- web_proxy
environment:
POSTGRES_DB: paperless
POSTGRES_USER: paperless
POSTGRES_PASSWORD: paperless
webserver:
image: ghcr.io/paperless-ngx/paperless-ngx:latest
restart: unless-stopped
depends_on:
- db
- broker
- gotenberg
- tika
networks:
- web_proxy
volumes:
- /home/andreas/paperless-ngx/data:/usr/src/paperless/data
- /home/andreas/paperless-ngx/media:/usr/src/paperless/media
- /home/andreas/paperless-ngx/export:/usr/src/paperless/export
- /home/andreas/paperless-ngx/consume:/usr/src/paperless/consume
env_file: docker-compose.env
environment:
PAPERLESS_REDIS: redis://broker:6379
PAPERLESS_DBHOST: db
PAPERLESS_TIKA_ENABLED: 1
PAPERLESS_TIKA_GOTENBERG_ENDPOINT: http://gotenberg:3000
PAPERLESS_TIKA_ENDPOINT: http://tika:9998
TRUSTED_PROXIES: "172.18.0.0/16" # das Docker-Subnetz, aus dem NPM kommt
USE_X_FORWARDED_FOR: True
PAPERLESS_ALLOWED_HOSTS: '*'
PAPERLESS_LOG_IP_ADDRESS_HEADER: X-Real-IP
PAPERLESS_LOGLEVEL: DEBUG
gotenberg:
image: docker.io/gotenberg/gotenberg:8.20
restart: unless-stopped
networks:
- web_proxy
command:
- "gotenberg"
- "--chromium-disable-javascript=true"
- "--chromium-allow-list=file:///tmp/.*"
tika:
image: docker.io/apache/tika:latest
restart: unless-stopped
networks:
- web_proxy
volumes:
pgdata:
redisdata:
networks:
web_proxy:
external: true
Hier ist jetzt einiges erläuterungsbedürftig. Mit der Angabe networks: - web_proxy sorge ich dafür, dass die Container im selben Docker-Netzwerk laufen, wie der NPM. Die gemounteten Volumes beginnen hier immer mit "/home/andreas/paperless-ngx". Ein symbolischer Pfad auf das Home-Share ("~/) würde im Docker-Container nicht aufgelöst werden können. Wenn der Nutzer, in dessen Kontext das Ganze läuft, nicht "andreas" heißt, dann muss das natürlich angepasst werden. Die Angaben zu Trusted Proxy, der Parameter USE_X_FORWARDED_FOR: True, PAPERLESS_ALLOWED_HOSTS: '*' und PAPERLESS_LOG_IP_ADDRESS_HEADER: X-Real-IP sind erforderlich, damit der Reverseproxy später die IP-Adressen, von denen aus auf das System zuzugreifen versucht wird, übermittelt und Paperless-NGX dem NPM auch vertraut. Das wird benötigt, um fail2ban auf diese Adressen ansetzen zu können. Die restlichen Parameter sind Standard. Diese Konfiguration kann dann ebenfalls mit
docker compose up -d
gestartet werden. Wenn keine Fehler aufgetreten sind, dann starten nun die benötigten Container. Das dauert ein wenig. Mit
docker ps
kann man sich den aktuellen Status ansehen und mit
docker logs paperless-ngx-webserver-1
kann man sich die Logs des Webservers ansehen. Sollte etwas nicht korrekt sein, dann würden hier Fehlermeldungen oder zumindest Warnungen auftreten.
Nun kann ich die Web-GUI des Reverse Proxys mit dem Webbrowser aufrufen. Sie ist unter der Adresse http://10.11.0.1:81 (nicht https) erreichbar. Die Warnung des Browsers, dass es sich um eine ungesicherte Verbindung handelt, kann ich ignorieren, da ich ja einen VPN-Tunnel nutze. Nach der frischen Installation lauten die Zugangsdaten
Email address: admin@example.com
Password: changeme
und ich werde natürlich als erstes aufgefordert, das zu ändern. Wichtig ist, hierbei eine echte eMail-Adresse einzugeben, sonst kann man kein Let's Encrypt-Zertifikat beziehen. Als erstes lege ich danach einen neuen Proxy-Host an.

Hier gebe ich die Domain ein, unter der der Dienst erreichbar sein wird. Diese Domain muss - anders als die hier dargestellte - vom Internet aus erreichbar sein und es muss ein A-Record eingestellt sein, der auf die IP-Adresse des Reverse-Proxy also des Servers zeigt. Das muss natürlich auch eine im Internet erreichbare IP-Adresse sein. Als Scheme ist http auszuwählen. Der Container kann nicht direkt mit https angesprochen werden. Als Forward Hostname ist der interne Name einzutragen, den der Container in der docker-compose.yml zugewiesen bekommen hat. Das ist nicht der Name, der mit docker ps angezeigt wird (also paperless-ngx-webserver-1) sondern hier schlicht "webserver". Da wir nichts anderes eingetragen haben, lauscht der Webserver auf Port 8000. Dann hake ich nur noch an "Cache Assets".
Nun wähle ich die Registerkarte SSL aus und hake die Optionen "Force SSL" und "HSTS Enabled" an, um den Browser zur ausschließlichen Nutzung von HTTPS zu zwingen und Man-in-the-Middle-Angriffe zu verhindern. Dann wähle ich unter "SSL Certificate" aus: "Request a new SSL certificate with Let's Encrypt". Unter Advanced trage ich in das Feld "Custom Nginx Configuration" folgenden Code ein:
# Wichtige Header für den Reverse Proxy
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;
proxy_set_header X-Forwarded-Host $host;
# Schutz vor Clickjacking: Verhindert das Einbetten der Seite in Frames add_header X-Frame-Options "DENY" always;
# Schutz vor MIME-Type Sniffing: Erzwingt die Einhaltung des Content-Type add_header X-Content-Type-Options "nosniff" always;
# Optional, aber empfohlen: Blockiert bekannte XSS-Angriffe add_header X-XSS-Protection "1; mode=block" always;
# Einstellungen für WebSockets (für Live-Updates in Paperless-NGX)
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
Diese Parameter sind erforderlich, damit die Anwendung hinter dem Proxy richtig funktioniert und gängige Angriffsszenarien abgewehrt werden. Wenn ich das nun mit OK bestätige, dann dauert das eine Weile, weil zunächst das SSL Zertitikat bei Let's Encrypt bezogen wird. Wenn hierbei keine Fehler auftreten, dann wird der neue Proxy Host mit dem Status "online" ausgewiesen und Paperless-NGX ist über die entsprechende Domain mittels https erreichbar. Ich kann nun die Weboberfläche von Paperless-NGX über die dafür ausgewählte Domain ansteuern und den ersten, den administrativen User anlegen. Dann kann ich mit der Nutzung von Paperless NGX beginnen. Aber ich bin noch nicht fertig mit meinem Projekt.
Noch ist der Server nicht vor Brute Force Attacken, also dem massenhaften, ggf. automatisierten Versuch, Benutzernamen und Passworte zu erraten, gesichert. Ganz am Anfang hatte ich ja fail2ban installiert, ohne weitere Konfigurationen daran vorzunehmen. Auf diese Weise sichert fail2ban zunächst die SSH-Schnittstelle gegen Brute Force Attacken ab, aber nicht mehr. Nun setze ich es auf die Anmeldung an Paperless-NGX an. Paperless-NGX speichert Logfiles in der Datei .../data/log/paperless.log. Das data-Verzeichnis habe ich in der docker-compose.yml auf den Pfad /home/andreas/paperless-ngx gemounted. Ich erstelle nun eine Filterdatei, die dieses Logfile auswertet und dort verzeichnete fehlerhafte Anmeldeversuche erkennt. Dazu erstelle ich die Datei /etc/fail2ban/filter.d/paperless-block.conf
sudo nano /etc/fail2ban/filter.d/paperless-block.conf
mit folgendem Inhalt:
[INCLUDES]
before = common.conf
[Definition]
# Paperless-Zeitstempel sind nicht nötig
datepattern =
# Regex für fehlgeschlagene Logins
failregex = \[paperless\.auth\]\s*Login failed for user `.*` from private IP `<HOST>`\.
ignoreregex =
Diese Filterdefinition erkennt nun das Vorkommen der Zeichenkette "Login failed for user... from private IP..." und ermittelt in der Variable <HOST> die IP-Adresse des "Angreifers". Unter ignoregex könnte man noch IP-Adressen erfassen, die von einer Sperre ausgenommen werden sollen. Darauf habe ich hier verzichtet, damit ich das Ganze auch testen kann. Später könnte ich dort die IP-Adressen aus meinem privaten Netzwerk ausschließen. Noch wird diese Filterdefinition aber nicht angewendet. Dazu muss erst ein Jail angelegt werden. Ich erstelle dazu die Datei /etc/fail2ban/jail.d/paperless.conf
sudo nano /etc/fail2ban/jail.d/paperless.conf
mit folgendem Inhalt:
[paperless]
enabled = true
filter = paperless-block
# Verwende die Multiport-Action für Container-Netz
banaction = docker-iptables
# Ports innerhalb des Docker-Netzes blockieren, Host bleibt unberuehrt
port = 80,443
# Logfile, das vom Host erreichbar ist
logpath = /home/andreas/paperless-ngx/data/log/paperless.log
backend = polling
# auch hier IPs vom Bann ausschliessen
ignoreip =
maxretry = 3
findtime = 600
bantime = 3600
Hier wird zunächst der eben definierte Filter verwendet. Dann wird eine banaction bestimmt. Das ist eine Aktion, die zum Aussperren der erkannten Übeltäter verwendet werden soll. Diese muss ich noch definieren. Geblockt werden sollen Zugriffe auf das Docker-Netzwerk. Das ist nun etwas schwer zu verstehen. Endanwender*innen kommen mit einer normalen, öffentliche IP-Adresse an. Diese ist zu sperren. Innerhalb des Docker-Netzwerks gibt es eigene Adressen aus dem Adressraum 172.17.0.0/24 und nicht zuletzt habe ich ja noch ein VPN definiert, mit dem Adressraum 10.11.0.0/24. Hier sollen aber die öffentlichen IPs der zugreifenden Systeme erkannt und ggf. geblockt werden.
Der Parameter logpath ist selbsterklärend. Wichtig ist danach der Parameter "backend = polling". Der bewirkt, dass tatsächlich dieses Logfile ausgewertet wird und nicht irgendwelche Systemprotokolle. Dann könnte ich auch hier noch einmal IP-Adressen vom Blockieren ausschließen. Der Parameter maxretry definiert natürlich die Anzahl der erlaubten Versuche. Findtime definiert den Suchzeitraum in Millisekunden, also den Zeitraum innerhalb dessen die Fehlerversuche gezählt werden. Hier habe ich mit 600 eine Minute definiert. Wenn ich innerhalb einer Minute dreimal versuche, mich mit falschen Credentials anzumelden, werde ich gesperrt. Bots, die durch das Netz schwirren, unternehmen sehr viele Zugriffsversuche in sehr kurzer Zeit und werden schnell erkannt. Wenn ich mich beim Anmelden zweimal vertippt habe, dann warte ich eine Minute und kann gefahrlos einen dritten Versuch unternehmen. Die bantime, also die Zeitspanne einer Sperrung beträgt hier 3600 Millisekunden, also eine Stunde. Diese Werte kann man nach Belieben ändern.
Nun muss ich aber auch noch die Banaction definieren. Das mache ich in der Datei /etc/fail2ban/action.d/docker-iptables.conf
sudo nano /etc/fail2ban/action.d/docker-iptables.conf
mit folgendem Inhalt:
[Definition]
actionstart =
actionstop =
actioncheck =
actionban = iptables -I DOCKER-USER -s <ip> -j DROP
actionunban = iptables -D DOCKER-USER -s <ip> -j DROP
User (bzw. deren IP-Adressen) werden auf der Firewall iptables gesperrt, indem ihre Zugriffsversuche gedropt werden. Da docker die ufw ignoriert, ist dieser Weg über iptables erforderlich. So funktioniert es aber. Wenn ich nun fail2ban mit
sudo systemctl restart fail2ban
neustarte, dann wird der neue Filter aktiv. Ich kann mir jederzeit den aktuellen Stand mit
sudo fail2ban-client status paperless
ansehen.
Wenn der Scanner nicht in der Lage ist, an eine eMail-Adresse zu scannnen, dann muss man in den consume-Ordner scannen, wenn man die Digitalisate nicht zu Fuß übertragen möchte. Wenn der Scanner aber nicht in das VPN integriert werden kann, dann muss man einen Umweg gehen. Hierzu gebe ich den consume-Ordner mittels Samba frei. Dann mounte ich den freigegebenen Ordner an meinem Client und gebe diesen wiederum im Netzwerk frei. Das ist zugegebenermaßen kein besonders eleganter Weg. Aber er funktioniert und ein besserer fällt mir nicht ein.

Samba kann ganz einfach mit dem Befehl
sudo apt install samba
installiert werden. Dann muss ich zunächst einen Samba-User anlegen, weil Samba eine eigene Benutzerverwaltung hat. Den Benutzer muss es als User auf dem System aber auch bereits geben. Ich verwende daher meinen üblichen Benutzeraccount und wende den Befehl
sudo smbpasswd -a andreas
an. Dann werde ich nach einem neuen Passwort gefragt (2 mal) und der Benutzer wird angelegt. Nun brauche ich nur noch eine Freigabe einzurichten. Dazu bearbeite ich die Datei /etc/samba/smb.conf mit Adminrechten
sudo nano /etc/samba/smb.conf
und füge am Ende ein:
[scans] path = /home/andreas/paperless-ngx/consume read only = no
guest ok = no
Mehr brauche ich hier nicht einzustellen. Ich habe ja den Zugriff auf den Server über die Firewall begrenzt und den Port für Samba ausschließlich für das VPN freigegeben. Mit
sudo systemctl daemon-reload
zwinge ich das System dazu, die Systemdateien neu einzulesen und mit
sudo systemtctl restart smbd.service
starte ich den Samba-Server neu. Nun ist die Freigabe im VPN für den User andreas verfügbar. Wenn ich einen Windows-Client nutzen würde, dann würde ich nun über den Windows-Explorer diese Netzwerkfreigabe als Netzwerklaufwerk verbinden und im Netz freigeben. Unter Linux muss ich zunächst auf dem Client die Freigabe mounten. Ich bearbeite dazu mit root-Rechten (auf dem Client, nicht auf dem Server) die Datei /etc/fstab
sudo nano /etc/fstab
und trage dort neu ein:
//10.11.0.1/scans /home/andreas/scans cifs _netdev,x-systemd.requires=wg-quick@wg1011.service,credentials=/pfad/zu/deiner/.smbcredentials,uid=1000,gid=1000 0 0
Der Parameter _netdev kennzeichnet die Freigabe als Netzwerk-Gerät. Systemd weiß dadurch, dass es mit dem Mounten warten soll, bis das grundlegende Netzwerk aktiv ist. Mit x-systemd.requires=wg-quick@wg1011.service wird Systemd mitgeteilt, dass die Mount-Unit erst gestartet werden darf, nachdem die Unit wg-quick@wg1011.service erfolgreich gestartet wurde und bereit ist.
Die Credentials für die Samba-Freigabe werden hier in der Datei "/root/.smbcredentials" erwartet. Diese muss ich zunächst mit
sudo nano /root/.smbcredentials
und dem Inhalt
username=andreas
password={smb-password}
erstellen. Dann muss ich die Berechtigungen für den Root-User dafür vergeben mit
sudo chown root:root /root/.smbcredentials
und mit
sudo chmod 600 /root/.smbcredentials
dafür sorgen, dass nur der Root-User diese sensiblen Daten lesen kann. Außerdem muss ich den lokalen Ordner scans ich noch erstellen:
mkdir scans
und dann einmal mounten (beim nächsten Start des Clients wird das automatisch gemountet, falls das VPN verfügbar ist. Wenn ich bisher keine SMB-Freigaben auf diesem Client nutze, muss ich zunächst die cifs-utils installieren
sudo apt install cifs-utils
Danach kann ich das Verzeichnis mounten.
sudo mount scans
oder mit
sudo mount -a
alles mounten, was in /etc/fstab steht und noch nicht gemountet wurde. Nun habe ich den Ordner consume des Paperless-NGX-Servers als Verzeichnis scans lokal verfügbar. Alle Dateien, die ich dort hineinschiebe und die Paperless-NGX verarbeiten kann, werden automatisch verarbeitet. Damit der Scanner auch das Verzeichnis nutzen kann, muss ich auf dem Client nun ebenfalls Samba installieren. Serverdienste auf einem Client sind natürlich nicht im Sinne des Erfinders. Das ist es, was ich mit nicht elegant gemeint habe. Aber nur so funktioniert es. Ich installiere also wie oben beschrieben auch auf dem Client den Samba-Server, füge ebenfalls den Samba-User andreas hinzu und erstelle in der Datei /etc/samba/smb.conf die Freigabe
sudo nano /etc/samba/smb.conf
mit folgendem Inhalt:
[scans] path = /home/andreas/scans
read only = no
guest ok = no
Nach
sudo systemctl daemon-reload
und
sudo systemctl restart smbd.service
steht der Ordner scans im lokalen Netzwerk unter der lokalen IP-Adresse meines Clients zur Verfügung und kann vom Scanner genutzt werden. Der Scanner greift also über die lokale Netzwerkadresse meines Clients auf die Freigabe zu welche eine Verbindung über das VPN auf den entfernten Server darstellt.
Wenn ich dem System Dokumente anvertraue, die ich vielleicht ansonsten nicht mehr aufbewahre, dann muss ich mich natürlich um eine zuverlässige Datensicherung kümmern. Hierfür bietet sich das Open Source Tool Duplicati an. Es lässt sich bequem über Docker installieren und bietet verschlüsselte, inkrementelle Backups auf verschiedenste Sicherungsziele an. Die Sicherungen auf dem selben System abzulegen, auf dem auch schon die Daten liegen, ist natürlich komplett sinnfrei. Daher brauche ich ein externes Sicherungsziel. Hier nutze ich, wie eingangs erklärt, Speicherlösungen bei Hosting-Anbietern. Die Einrichtung der Speichers bei externen Hostern führe ich hier nicht aus, weil es viele verschiedene Anbieter und Konfigurationsoberflächen gibt. Im Ergebnis setze ich im Folgenden voraus, dass es einen Speicher gibt, der per SFTP (SSH) angesprochen werden kann.
Paperless-NGX bietet mit dem document-exporter eine komfortable Möglichkeit, alle Dokumente, Datenbankinhalte und Strukturen zu exportieren. Das Ergebnis dieses Exports kann dann gesichert werden und damit ist auch eine Wiederherstellung des Systems jederzeit möglich. Der document-exporter wird mit
docker exec -it paperless-ngx-webserver-1 document_exporter /usr/src/paperless/export
aufgerufen bzw. genutzt. Hierbei muss der Pfad angegeben werden, den der Export-Ordner innerhalb des Docker-Containers nutzt. Gemountet ist dieser Ordner auf den Pfad /home/andreas/paperless-ngx/export. Dieser Export soll natürlich einmal am Tag ausgeführt werden, also definiere ich einen Cronjob dafür. Da der Job im normalen User-Kontext also ohne Adminitrationsrechte ausgeführt werden soll, kann ich ihn in der Crontab für den Standarduser einrichten. Ich rufe die crontab-Bearbeitung mit
crontab -e
auf. Wenn ich bisher keinen Standard-Editor für die Bearbeitung der Crontab angegeben habe, werde ich nun danach gefragt. Hier wähle ich den vorgeschlagenen Editor nano aus:
no crontab for andreas - using an empty one
Select an editor. To change later, run 'select-editor'.
1. /bin/nano <---- easiest
2. /usr/bin/vim.basic
3. /usr/bin/vim.tiny
4. /bin/ed
Choose 1-4 [1]: 1
An das Ende dieser Datei trage ich dann ein:
0 7 * * * docker exec -it paperless-ngx-webserver-1 document_exporter /usr/src/paperless/export
Am Anfang der Zeile wird definiert, wann der Cronjob ausgeführt werden soll. An erster Stelle werden die Minuten, dann die Stunden, dann der Tag des Monats (1-31), der Monat (1-12), der Wochentag (Sonntag = 0, Samstag = 7) angegeben. Darauf folgt das auszuführende Kommando. In diesem Fall wird jeden Morgen um 7:00 Uhr ein Export ausgeführt. Dabei werden vorhandene Dateien überschrieben, wenn im System eine neuere verfügbar ist. Es werden die Ursprungsdateien, die PDF-Repräsentationen, Web-Vorschauen und in einer Manifest-Datei alle Metadaten und Datenbankinhalte gespeichert. Den Inhalt des Export-Ordner muss ich nun natürlich sichern.
Auch Duplicati installiere ich per Docker und Docker-Compose. Dann erstelle ich ein Verzeichnis duplicati:
mkdir duplicati
Darin erstelle ich dann die Datei docker-compose.yml
nano docker-compose.yml
mit folgendem Inhalt:
services:
duplicati:
image: lscr.io/linuxserver/duplicati:latest
container_name: duplicati
environment:
- PUID=0
- PGID=0
- TZ=Europe/Berlin
- SETTINGS_ENCRYPTION_KEY=strenggeheimerundkomplexerkey
- DUPLICATI__WEBSERVICE_PASSWORD=strenggeheimesundkomplexespasswort
volumes:
- ./config:/config
- /home/andreas/paperless-ngx/export:/paperless-export
- /home/andreas/sicherungsverzeichnis1:/source1
ports:
- 10.11.0.1:8200:8200
networks:
- web_proxy
restart: unless-stopped
networks:
web_proxy:
external: true
Der Encryption-Key wird als Teil der Verschlüsselung der Sicherungsdaten verwendet und das Webservice-Passwort wird benötigt, um sich an der Weboberfläche anzumelden (wer hätte das gedacht?).
Hier habe ich das Exportverzeichnis von Paperless-NGX als Volume gemountet und zusätzlich ein Verzeichnis namens "sicherungsverzeichnis1". Hier kann man beliebige Verzeichnisse angeben, die dann später von der Weboberfläche aus und für den Dockercontainer erreichbar sind. Das zweite Verzeichnis habe ich angegeben, damit ich später aus duplicati heraus einzelne Files an anderer Stelle zurücksichern kann, als dem Ursprungspfad. Mit der Angabe unter "ports" binde ich die Duplicati-Weboberfläche vor allem an die VPN-Adresse. Duplicati hätte nun nicht unbedingt im selben Docker-Netwzerk laufen müssen, wie der NPM und Paperless-NGX, aber so vermeide ich unnötige Netzwerke auf dem Server.
Duplicati starte ich nun mit
docker compose up -d
und kann mich dann an der Weboberfläche anmelden. Diese rufe ich mit 10.11.0.1:8200 auf. Sie ist nur über das VPN erreichbar.
Wenn ich mich an der Weboberfläche von Duplicati anmelde, dann moniert mein Browser natürlich die unsichere Verbindung. Das kann ich aber getrost ignorieren, weil ich mich ja innerhalb meines VPN bewege. Der Dialog für das Hinzufügen eines neuen Backup-Jobs ist größtenteils selbst erklärend.

Unter den allgemeinen Sicherungseinstellungen vergebe ich zunächst einen Namen für die Sicherung. Der darf ruhig sprechend sein. Eine Sicherungsbeschreibung kann ich auch noch eingeben. Das bewirkt aber nichts. Interessanter ist da schon die Verschlüsselung. Ich kann hier wählen zwischen AES-256, was bereits integriert ist oder GNU Privacy Guard, was auf den allermeisten Linux-Systemen bereits installiert ist. Ich entscheide mich für die integrierte Variante. Außerdem muss ich mir ein Passwort ausdenken. Hier werden auch schwache Passworte akzeptiert, aber man sollte sich schon etwas Mühe geben.
Bei den Backup-Destinationen wähle ich natürlich SSH aus. Auf der nächsten Seite muss ich dann die Zugangsdaten für den externen Speicher eingeben. Bei einer Storage-Box von Hetzner ist das z.B. accountname.your-storagebox.de, ansonsten könnte das eine IP-Adresse eines entfernten Servers oder auch eine Domain sein. Der Standard-Port für SSH ist 22, bei der Storage-Box von Hetzner ist es 23. Auf der Firewall habe ich ausgehenden Traffik nicht begrenzt. Darum brauche ich mir keine Gedanken zu machen, ob ich einen Port auf der Firewall öffnen muss. Ordnerpfad ist natürlich der Pfad, in den auf dem entfernten System gesichert werden soll. Den hier angegebenen Ordner muss es im Ziel schon geben, sonst läuft die Sicherung auf einen Fehler. Das war in früheren Versionen von Duplicati noch anders. Da wurde man dann gefragt, ob der Ordner angelegt werden soll.. Username ist natürlich der Username in dessen Berechtigungskontext auf dem Zielsystem die Sicherungen abgelegt werden sollen.
Die Autentifizierungsmethode wählt man mit "Erweiterte Option hinzufügen" aus. Hier reicht eine Authentifizierung mit Passwort aus. Duplicati unterstützt auch SSH-Key-Authentifizierung. Allerdings werden nur Schlüssel im PEM-Format erlaubt und die Einbindung ist auch recht kompliziert. Da die Passwortübertragung verschlüsselt erfolgt, mache ich es mir einfach und wähle Passwortauthentifizierung aus. Neben dem Passwort prüft Duplicati den Fingerprint der Gegenseite. Den erwartet es als MD5-Hash. Da es je nach Hosting-Anbieter nicht ganz einfach ist, den Fingerprint der Gegenseite im MD5-Format zu bekommen, wende ich einen kleinen Trick an. Ich füge zunächst unter "Erweiterte Option hinzufügen" "SSH Fingerprint" hinzu und schreibe in das entsprechende Feld irgend etwas hinein. Später, wenn ich mit diesen unsinnigen Daten zu sichern versuche, erhalte ich eine Fehlermeldung, in der mir der korrekte Fingerprint angezeigt wird. Dazu komme ich später noch. Ich klicke also nicht auf "Test destination", sondern erst einmal auf Weiter. Hier wähle ich die zu sichernden Daten aus. In meinem Fall habe ich ja den Export-Ordner von Paperless-NGX in das Verzeichnis "source" gemountet und darum wähle ich das natürlich aus.
Die Parameter auf der Seite Zeitplan sind selbsterklärend. Ich stelle meine Sicherungen auf z.B. 8:00 Uhr morgens ein, weil der Export ja jeden Morgen um 7:00 Uhr läuft. Auf der Seite Optionen brauche ich bei der Zielvolumengröße nichts zu ändern. Interessanter sind die Retention-Regeln, die ich unter "Sicherungsaufbewahrung" einstellen kann. Alle Sicherungen immer aufzubewahren ist natürlich nicht sinnvoll.

Ich könnte auswählen, dass alle Sicherungen gelöscht werden, die älter sind als n Tage, Wochen, Monate oder Jahre. Ich könnte aber auch vorgeben, dass eine bestimmte Anzahl von Sicherungen aufbewahrt werden und alle anderen (älteren) gelöscht werden. Wenn ich "intelligente Sicherungsaufbewahrung" auswähle, dann werden die Sicherungen der letzten 7 Tage, jeweils eine der letzten 4 Wochen und jeweils eine der letzten 12 Monate aufbewahrt. Alternativ kann ich eine individuelle Aufbewahrungsregel definieren. Die Syntax wird hier ganz gut erklärt. Mir reicht hier für's Erste die intelligente Aufbewahrungsregel. Unter "Optionen für Profis" könnte ich noch sehr viele erweiterte Optionen hinzufügen. Das ist aber wirklich etwas für Profis, darum lasse ich einstweilen die Finger davon. Wenn ich auf "Senden" klicke, dann wird der Sicherungsjob gespeichert. Die Sicherung würde dann um 6:00 Uhr starten und zunächst auf einen Fehler laufen, wegen des falschen SSH-Fingerprints. Das warte ich natürlich nicht ab, sondern klicke auf der Startseite auf "Start". Nun läuft der Job auf den erwarteten Fehler. In der Fehlermeldung wird mir auch ein Link auf das Protokoll angezeigt, den ich anklicke. Dort wird ein Fehler ausgewiesen, der auch den erwarteten SSH-Fingerprint beginnend mit "ssh-ed25519 256" enthält. Den kopiere ich einfach aus der Fehlermeldung heraus. Ich gehe erneut in die Bearbeitung des Sicherungsjobs und trage den korrekten Schlüssel auf der Seite mit der Sicherungszieldefinition ein. Wenn ich nun auf "Test destination" klicke, wird mir angezeigt, dass die Verbindung funktioniert. Ich kann die Bearbeitung der Sicherung abschließen und die erste Sicherung anstoßen. Das sollte nun funktionieren.
Nun habe ich also einen Server bei einem Hoster, auf dem Paperless-NGX läuft. Die Weboberfläche von Paperless-NGX ist aus dem Internet über eine (Sub-)Domain erreichbar und mit einem gültigen SSL-Zertifikat von Let's Encrypt abgesichert. Die SSL-Absicherung und die Verbindung mit dem Zertifikat werden durch einen Reverse-Proxy besorgt. Paperless-NGX ist ausschließlich über den Reverse-Proxy ansprechbar. Somit kann die Absicherung mit SSL niemand umgehen. Die Remote-Konsole des Servers sowie die Weboberflächen des Reverse Proxys und von Duplicati sind ausschließlich über das VPN erreichbar. Die Firewall blockiert unerlaubte Zugriffe aus dem Netz und fail2ban sperrt IP-Adressen aus, wenn sie mehr als dreimal versuchen, mit falschen Zugangsdaten auf die Remoteshell oder Paperless-NGX zuzugreifen. Die Datensicherung erfolgt auf ein System bei einem anderen Hoster, also auch in einem anderen Rechenzentrum. Die Sicherungen finden verschlüsselt, komprimiert und inkrementell statt. Auch der Übertragungsweg der Sicherungsdaten ist verschlüsselt.
Damit habe ich ein System geschaffen, das eine sichere und zuverlässige Verarbeitung meiner Dokumente ermöglicht, ohne dass ich zuhause Server dafür betreiben muss. Die laufenden Kosten für die beiden Betreiber belaufen sich auf knapp 10,00 Euro im Monat. Die in der Einleitung genannten Ziele sind alle erreicht.