Kurz gesagt
Problem: Ein Nextcloud-Container läuft in einer Minute. Damit er schnell ist, über HTTPS erreichbar, ohne Warnungen in der Admin-Übersicht und sicher zu sichern, braucht es deutlich mehr als docker run nextcloud.
Lösung: Ein Compose-Projekt mit Nextcloud, PostgreSQL, Redis und einem Cron-Container, veröffentlicht über Traefik, Anmeldung über Authentik, Dokumente im Browser mit dem eingebauten Collabora-Server und ein nächtliches Backup im Wartungsmodus.
Getestet mit: Nextcloud 35 (offizielles Image nextcloud:35, PHP 8.5), PostgreSQL 16, Redis, Traefik 3.7, Authentik, Docker Compose auf einem kleinen Heimserver
Nextcloud ist der Dienst in meinem Homelab, den die ganze Familie wirklich nutzt: Dateien, Kalender, Kontakte, Notizen und seit Kurzem auch Office-Dokumente. Deshalb ist es auch der Dienst, den ich am wenigsten kaputt machen möchte. Dieser Artikel geht meine Konfiguration durch: die Container, die Einstellungen, auf die es hinter einem Reverse Proxy ankommt, Single Sign-on, Nextcloud Office, die Pflege nach Updates und wie ich alles sichere. Er baut auf meinem Traefik-Artikel auf; wenn du noch keinen Reverse Proxy hast, fang dort an.
1. Der Stack im Überblick
| Container | Image | Aufgabe |
|---|---|---|
nextcloud | nextcloud:35 | Die Web-App (Apache + PHP) |
postgres | postgres:16 | Die Datenbank. Deutlich besser als SQLite, sobald mehr als eine Person synchronisiert |
redis | redis | Datei-Sperren und gemeinsamer Cache |
nextcloud-cron | nextcloud:35 | Führt alle 5 Minuten die Hintergrundjobs aus |
Das Tag nextcloud:35 ist eine bewusste Entscheidung. Es folgt den Minor- und Patch-Versionen von Version 35, mein Update-Skript (es führt dreimal pro Woche docker compose pull && docker compose up -d aus) holt Sicherheitsupdates also von selbst. Eine neue Hauptversion kommt dagegen nie überraschend. Nextcloud kann immer nur eine Hauptversion auf einmal springen, und vor diesem Schritt möchte ich die Release Notes lesen und ein frisches Backup haben.
2. Die .env-Datei
Alles, was sich von Installation zu Installation unterscheidet, und jedes Passwort steht in einer .env-Datei neben der Compose-Datei. Mach sie nur für ihren Besitzer lesbar: chmod 600 .env.
NEXTCLOUD_IMAGE_TAG=nextcloud:35
NEXTCLOUD_HOSTNAME=cloud.example.com
NEXTCLOUD_URL=https://cloud.example.com
NEXTCLOUD_TIMEZONE=Europe/Berlin
NEXTCLOUD_DB_NAME=nextcloud
NEXTCLOUD_DB_USER=nextcloud
NEXTCLOUD_DB_PASSWORD=bitte-aendern-lang-und-zufaellig
NEXTCLOUD_REDIS_PASSWORD=bitte-auch-aendern
# wird nur beim allerersten Start gelesen
NEXTCLOUD_ADMIN_USERNAME=admin
NEXTCLOUD_ADMIN_PASSWORD=bitte-ebenfalls-aendern
Gute Passwörter erzeugst du zum Beispiel mit openssl rand -base64 32. Admin-Benutzer und -Passwort liest Nextcloud nur, wenn es sich beim ersten Start installiert. Sie später in der .env zu ändern, bewirkt nichts.
3. Datenbank, Redis und Cron
networks:
nextcloud-network:
proxy:
external: true
volumes:
nextcloud-app:
nextcloud-postgres:
redis-data:
services:
postgres:
image: postgres:16
restart: unless-stopped
volumes:
- nextcloud-postgres:/var/lib/postgresql/data
environment:
POSTGRES_DB: ${NEXTCLOUD_DB_NAME}
POSTGRES_USER: ${NEXTCLOUD_DB_USER}
POSTGRES_PASSWORD: ${NEXTCLOUD_DB_PASSWORD}
networks:
- nextcloud-network
healthcheck:
test: ["CMD", "pg_isready", "-q", "-d", "${NEXTCLOUD_DB_NAME}", "-U", "${NEXTCLOUD_DB_USER}"]
interval: 10s
timeout: 5s
retries: 3
start_period: 60s
redis:
image: redis
restart: unless-stopped
command: ["redis-server", "--requirepass", "${NEXTCLOUD_REDIS_PASSWORD}"]
volumes:
- redis-data:/data
networks:
- nextcloud-network
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 3
nextcloud-cron:
image: ${NEXTCLOUD_IMAGE_TAG}
restart: unless-stopped
entrypoint: /cron.sh
volumes:
- nextcloud-app:/var/www/html
- /srv/nextcloud/data:/var/www/html/data
networks:
- nextcloud-network
Ein paar Details, die man leicht übersieht:
- Datenbank und Redis hängen nur im
nextcloud-network. Sie haben keine veröffentlichten Ports, und Traefik sieht sie nicht. Nur Nextcloud spricht mit ihnen. - Die Healthchecks sind hier wichtig. Nextcloud wartet auf
service_healthy(siehe unten) und startet seine Installation oder sein Upgrade deshalb nie gegen eine Datenbank, die noch hochfährt. - Der Cron-Container nutzt dasselbe Image und dieselben Volumes wie Nextcloud, startet aber
/cron.sh, das alle 5 Minutencron.phpaufruft. Stell Nextcloud nach dem ersten Start von AJAX auf Cron um:occ background:cron(oder Verwaltungseinstellungen → Grundeinstellungen).
4. Der Nextcloud-Container
nextcloud:
image: ${NEXTCLOUD_IMAGE_TAG}
restart: unless-stopped
volumes:
- nextcloud-app:/var/www/html
- /srv/nextcloud/data:/var/www/html/data
environment:
TZ: ${NEXTCLOUD_TIMEZONE}
POSTGRES_HOST: postgres
POSTGRES_DB: ${NEXTCLOUD_DB_NAME}
POSTGRES_USER: ${NEXTCLOUD_DB_USER}
POSTGRES_PASSWORD: ${NEXTCLOUD_DB_PASSWORD}
REDIS_HOST: redis
REDIS_HOST_PORT: 6379
REDIS_HOST_PASSWORD: ${NEXTCLOUD_REDIS_PASSWORD}
NEXTCLOUD_ADMIN_USER: ${NEXTCLOUD_ADMIN_USERNAME}
NEXTCLOUD_ADMIN_PASSWORD: ${NEXTCLOUD_ADMIN_PASSWORD}
NEXTCLOUD_TRUSTED_DOMAINS: ${NEXTCLOUD_HOSTNAME}
# hinter dem Reverse Proxy
OVERWRITEHOST: ${NEXTCLOUD_HOSTNAME}
OVERWRITEPROTOCOL: https
OVERWRITECLIURL: ${NEXTCLOUD_URL}
TRUSTED_PROXIES: 172.16.0.0/12
# große Uploads und Office
PHP_MEMORY_LIMIT: 8G
PHP_UPLOAD_LIMIT: 8G
networks:
- nextcloud-network
- proxy
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:80/"]
interval: 10s
timeout: 5s
retries: 3
start_period: 90s
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
Zwei Speicherorte. Das benannte Volume nextcloud-app enthält die Anwendung selbst, samt config/config.php und den Apps, die du aus dem App-Store installierst (custom_apps). Die Dateien der Benutzer liegen in einem Bind-Mount auf einer großen Platte (hier /srv/nextcloud/data). Durch die Trennung kann ich die Daten auf eine eigene SSD legen und beides unterschiedlich sichern. Vergiss nur das Volume nicht: config.php enthält das Secret und den Passwort-Salt der Instanz, und ohne sie wird eine Wiederherstellung nur aus den Daten deutlich schwieriger.
Die Reverse-Proxy-Einstellungen. Nextcloud bekommt von Traefik nur einfaches HTTP auf Port 80. Ohne diese vier Variablen baut es falsche Links, lehnt Anmeldungen über den „nicht vertrauenswürdigen“ Hostnamen ab oder zeigt jeden Besucher mit der IP-Adresse von Traefik:
| Variable | Wozu |
|---|---|
OVERWRITEHOST | Der Hostname, den Nextcloud in Links und Weiterleitungen verwendet |
OVERWRITEPROTOCOL=https | Links beginnen mit https, obwohl die Anfrage innerhalb von Docker http ist. Ohne diese Variable laufen Anmelde-Weiterleitungen im Kreis oder brechen ab |
OVERWRITECLIURL | Richtige Links in E-Mails und in allem, was Hintergrundjobs oder occ erzeugen |
TRUSTED_PROXIES | Nextcloud glaubt den X-Forwarded-For-Header nur von diesen Adressen. Dann zeigen Log, Brute-Force-Schutz und „Letzte Anmeldung“ die echte IP des Clients |
172.16.0.0/12 deckt die Standard-Netzbereiche von Docker ab, in denen Traefik läuft. Trag ein, was docker network inspect proxy auf deinem Host anzeigt, und nicht mehr: Jeder Bereich, dem du vertraust, kann Client-IPs fälschen.
5. Veröffentlichen über Traefik
labels:
- "traefik.enable=true"
- "traefik.docker.network=proxy"
- "traefik.http.routers.nextcloud.rule=Host(`cloud.example.com`)"
- "traefik.http.routers.nextcloud.entrypoints=websecure"
- "traefik.http.routers.nextcloud.tls.certresolver=letsencrypt"
- "traefik.http.routers.nextcloud.middlewares=nextcloud-dav,nextcloud-wellknown,nextcloud-compress,nextcloud-hsts"
- "traefik.http.services.nextcloud.loadbalancer.server.port=80"
# CalDAV/CardDAV-Erkennung für Handys und Kalender-Apps
- "traefik.http.middlewares.nextcloud-dav.redirectregex.permanent=true"
- "traefik.http.middlewares.nextcloud-dav.redirectregex.regex=https?://([^/]*)/.well-known/(card|cal)dav"
- "traefik.http.middlewares.nextcloud-dav.redirectregex.replacement=https://$${1}/remote.php/dav/"
# alle anderen .well-known-URLs (webfinger, nodeinfo, ...)
- "traefik.http.middlewares.nextcloud-wellknown.redirectregex.permanent=true"
- "traefik.http.middlewares.nextcloud-wellknown.redirectregex.regex=https?://([^/]*)(/.well-known[^#]*)"
- "traefik.http.middlewares.nextcloud-wellknown.redirectregex.replacement=https://$${1}/index.php$${2}"
- "traefik.http.middlewares.nextcloud-compress.compress=true"
- "traefik.http.middlewares.nextcloud-hsts.headers.stsSeconds=15552000"
Die beiden Weiterleitungen beheben die häufigste Warnung in der Admin-Übersicht („Dein Webserver ist nicht richtig eingerichtet, um /.well-known/caldav aufzulösen“). Außerdem finden Handys damit Kalender und Kontakte, wenn du nur den Hostnamen eingibst. Das $$ ist nötig, weil Compose ${1} sonst als Variable liest.
HSTS (die Zeile mit stsSeconds) sagt Browsern, dass sie diesen Host die nächsten 180 Tage nur noch über HTTPS aufrufen sollen. Das beseitigt eine weitere Warnung. Bei mir setzt eine allgemeine Security-Header-Middleware am Entrypoint websecure HSTS ohnehin für jeden Dienst (siehe den Artikel zum Absichern). Die Zeile bleibt trotzdem hier, damit der Nextcloud-Stack HSTS auch dann bekommt, wenn ich daran einmal etwas ändere.
Achtung, nur eine Middlewares-Zeile pro Router: Setzt eine Compose-Datei traefik.http.routers.nextcloud.middlewares zweimal, gibt es keinen Fehler. Die zweite Zeile ersetzt die erste stillschweigend. Genau das stand lange in meiner eigenen Datei: Eine frühere Zeile mit einer Buffering-Middleware war nie aktiv, und das ist mir erst beim Schreiben dieses Artikels aufgefallen (docker inspect zeigt die Labels, die der Container wirklich hat). Schreib alle Middlewares kommagetrennt in eine Zeile.
Für große Uploads brauchst du übrigens keine Buffering-Middleware. Traefik hat standardmäßig keine Größenbegrenzung für Anfragen, und die Nextcloud-Clients laden große Dateien ohnehin in Stücken hoch.
6. Anmeldung mit Authentik
Wie die meisten meiner Dienste nutzt Nextcloud Single Sign-on mit Authentik. Ich verwende die offizielle App OpenID Connect user backend (user_oidc). Die Einrichtung auf der Authentik-Seite (ein OAuth2/OpenID-Provider plus eine Anwendung) ist dieselbe wie in meinem Artikel zu WordPress-SSO. Die Redirect-URI für Nextcloud ist https://cloud.example.com/apps/user_oidc/code.
Danach im Nextcloud-Container:
alias occ='docker compose exec -u www-data nextcloud php occ'
occ app:install user_oidc
occ user_oidc:provider Authentik \
--clientid="<Client-ID aus Authentik>" \
--clientsecret="<Client-Secret aus Authentik>" \
--discoveryuri="https://id.example.com/application/o/nextcloud/.well-known/openid-configuration" \
--scope="openid email profile" \
--mapping-uid=preferred_username \
--mapping-display-name=name \
--mapping-email=email \
--unique-uid=0
Entscheidend sind die letzten beiden Optionen. --mapping-uid=preferred_username zusammen mit --unique-uid=0 macht den Nextcloud-Benutzernamen gleich dem Authentik-Benutzernamen statt eines langen Hashes. Ein Authentik-Benutzer anna landet so im bestehenden Nextcloud-Konto anna mit all ihren Dateien, und die Pfade bleiben lesbar.
Die Anmeldeseite zeigt jetzt einen Button „Log in with Authentik“. Die normale Nextcloud-Anmeldung bleibt darunter verfügbar. Die behalte ich mit Absicht: Wenn Authentik ausfällt, funktioniert das lokale Admin-Konto trotzdem. Desktop- und Handy-Clients melden sich über den Browser an und nutzen damit ebenfalls SSO.
Tipp: Setz die Forward-Auth-Middleware von Authentik nicht vor Nextcloud. Sie bricht die Sync-Clients und WebDAV, die sich nicht bei jeder Anfrage über den Browser anmelden können. Nimm stattdessen die OIDC-App.
7. Nextcloud Office ohne zusätzlichen Container
Um .docx-, .xlsx- und .odt-Dateien im Browser zu öffnen, nutze ich Nextcloud Office mit dem eingebauten CODE-Server (Collabora Online Development Edition). Er läuft im Nextcloud-Container: kein zusätzlicher Container, kein zusätzlicher Hostname, kein zusätzliches Zertifikat. Für einen Haushalt mit einer Handvoll Benutzer reicht das völlig.
occ app:install richdocumentscode # der eingebaute Collabora-Server (auf ARM: richdocumentscode_arm64)
occ app:install richdocuments # die App Nextcloud Office
occ richdocuments:activate-config
Danach sollte unter Verwaltungseinstellungen → Office stehen, dass der eingebaute Server verwendet wird. Die WOPI-URL zeigt auf https://cloud.example.com/custom_apps/richdocumentscode/proxy.php?req=.
Achtung, Security-Header: Der Editor läuft in einem iframe innerhalb von Nextcloud. Ein globaler Header X-Frame-Options: DENY oder SAMEORIGIN vom Reverse Proxy macht daraus ein leeres, graues Dokument. Nextcloud setzt seine eigenen Frame-Header, überlass diesen Header also der App.
Prüf auch, ob die Version der Office-App zu deiner Nextcloud-Hauptversion passt. Nach einem Upgrade bleibt manchmal eine ältere, nicht kompatible Version installiert; occ app:update --all behebt das.
8. Saubere Admin-Übersicht nach einem Update
Nach einem Hauptversions-Upgrade zeigt Verwaltungseinstellungen → Übersicht meistens ein paar neue Warnungen. Diese Befehle habe ich gebraucht, ungefähr in dieser Reihenfolge (mit frischem Backup):
# neue Datenbank-Indizes, die mit dem Upgrade kamen (dauert Sekunden bis Minuten)
occ db:add-missing-indices
# nur wenn die Übersicht es vorschlägt; kann bei großen Instanzen dauern
occ maintenance:repair --include-expensive
# Telefonnummern in Profilen ohne Ländervorwahl
occ config:system:set default_phone_region --value="DE"
# aufwendige Hintergrundjobs nachts ausführen (Stunde in UTC, 1 = 01:00-05:00)
occ config:system:set maintenance_window_start --type=integer --value=1
# dieselben Prüfungen wie die Admin-Übersicht, auf der Kommandozeile
occ setupchecks
Den Speicher-Cache richtet das offizielle Image selbst ein: APCu für den lokalen Cache und Redis für die Datei-Sperren, sobald REDIS_HOST gesetzt ist. Prüfen kannst du das mit occ config:system:get memcache.locking.
9. Backups
Nextcloud wird jede Nacht von einem Skript auf dem Host gesichert, nicht innerhalb des Stacks. Die Reihenfolge ist wichtig:
occ maintenance:mode --on, damit sich während der Kopie keine Datei und keine Datenbankzeile ändertpg_dumpder Datenbank in eine einfache SQL-Datei (nie die laufenden Datenbankdateien kopieren)rsyncdes Datenordners in einen Snapshot mit Datum, mit Bandbreitenlimit, damit die Platten nutzbar bleiben- eine Kopie von
config/,custom_apps/undthemes/aus dem App-Volume, dazu die Compose-Datei und die.env occ maintenance:mode --off, in einemtrap, damit das auch passiert, wenn ein Schritt fehlschlägt
In einer normalen Nacht dauert der Wartungsmodus ein paar Minuten. Unveränderte Dateien sind zwischen den Snapshots per Hardlink verknüpft, 14 Tage Verlauf kosten also kaum mehr Platz als eine Kopie. Wie Snapshots, Datenbank-Dumps und die Offline-Kopie zusammenspielen, steht in meinem Backup-Artikel.
Tipp: Führ nach einer Wiederherstellung occ maintenance:data-fingerprint aus. Damit erfahren die Sync-Clients, dass der Server in der Zeit zurückgesprungen ist. Sie fragen dann nach, bevor sie neuere Dateien überschreiben, statt still in irgendeine Richtung zu synchronisieren.
10. Wenn etwas nicht funktioniert
| Symptom | Häufige Ursache |
|---|---|
| „Zugriff über eine nicht vertrauenswürdige Domain“ | Der Hostname fehlt in trusted_domains. NEXTCLOUD_TRUSTED_DOMAINS wirkt nur bei der ersten Installation; später occ config:system:set trusted_domains 1 --value=cloud.example.com |
| Die Anmeldung landet wieder auf der Anmeldeseite, oder Links beginnen mit http:// | OVERWRITEPROTOCOL=https fehlt |
| Jeder Log-Eintrag zeigt dieselbe interne IP | TRUSTED_PROXIES enthält das Netz von Traefik nicht |
| „Dein Webserver ist nicht richtig eingerichtet, um /.well-known/caldav aufzulösen“ | Die Weiterleitungs-Middlewares sind nicht aktiv. Prüf mit docker inspect, welche Middlewares der Router wirklich bekommen hat |
| Office-Dokumente öffnen sich als leere graue Seite | Ein X-Frame-Options-Header vom Proxy oder eine Office-App-Version, die nicht zur Nextcloud-Version passt |
| „Letzte Hintergrundjob-Ausführung lief vor … Stunden“ | Der Cron-Container läuft nicht, oder Nextcloud steht noch auf AJAX |
Die nützlichsten Befehle für den Rest:
# die neuesten Fehler aus dem Nextcloud-Log
occ log:tail 20
# worüber sich die Admin-Übersicht beschweren würde
occ setupchecks
# welche Labels (und damit welche Traefik-Middlewares) der Container wirklich hat
docker inspect nextcloud-nextcloud-1 --format '{{json .Config.Labels}}'
Was ich gelernt habe
- Die Hauptversion festlegen. Mit
nextcloud:35bekomme ich Sicherheitsupdates automatisch, und das große Upgrade passiert, wenn ich es entscheide, mit Backup. - Die Reverse-Proxy-Einstellungen sind nicht optional. Die meisten seltsamen Nextcloud-Probleme hinter Traefik lagen an
OVERWRITEPROTOCOLoderTRUSTED_PROXIES. - Prüfen, was wirklich läuft. Ein Label, das von einem späteren überschrieben wird, erzeugt keinen Fehler.
docker inspectzeigt die Wahrheit, die Compose-Datei nur die Absicht. - Das App-Volume gehört ins Backup. Lange habe ich nur den Datenordner und die Datenbank gesichert. Ohne
config.phpist eine Wiederherstellung deutlich schwieriger. - SSO über OIDC, nicht über Forward-Auth. Die Sync-Clients funktionieren weiter, und die lokale Anmeldung ist mein Notausgang.