← Übersicht  ·  Skripte & Dateien  ·  cloud-host · Dienst

Nextcloud auf Hetzner

Die eigene Cloud für Dateien, Kalender, Kontakte und Fotos – unter cloud.seb4u.com, auf deinem Server. Mit den Fallen, die zuverlässig zuschlagen: der Cron-Job, RAM (Random Access Memory)/Redis/PHP (PHP Hypertext Preprocessor), S3 als Primärspeicher, Collabora Office, .well-known für CalDAV (Calendaring Extensions to WebDAV), ein Backup mit getestetem Restore – und Updates nur eine Hauptversion pro Schritt.

Stand: 30. August 2026 Docker Compose + Traefik ab ~8 €/Monat

Platzhalter einsetzen – nur im Browser

Was & welche Variante

Nextcloud = Dateien-Sync (wie Dropbox) + Kalender/Kontakte (CalDAV/CardDAV) + Fotos + optional Office, Notizen, Aufgaben, Talk. Selbst gehostet, auf deiner Platte.

Wegfür wenKompromiss
Nextcloud AIO (All-in-One)Haushalt, wenig Docker-Erfahrungein Mastercontainer verwaltet alles (DB, Redis, Collabora, Backup) – bequem, aber weniger Kontrolle
Manuelles Compose (diese Anleitung)du willst jeden Baustein sehen, Traefik teilenmehr Handarbeit, dafür volle Kontrolle & Integration in den bestehenden Stack
Managed (Hetzner Managed Nextcloud, IONOS, …)null Server-Arbeitteurer, weniger Freiheit – aber Überlegung wert
Nur Datei-Sync ohne Weboberfläche2–3 Geräte, keine FreigabenSyncthing ist viel leichter – aber keine Kalender/Web/Freigabe-Links
Traefik443 → cloud.seb4u.com, TLS (Transport Layer Security), .well-known-Redirects
nginxstatische Dateien + FastCGI zu php-fpm
nextcloudphp-fpm: die Anwendung. Daneben ein cron-Container.
mariadbMetadaten, Freigaben, Aktivitäten
redisCache + File-Locking (verhindert Sync-Konflikte)
SpeicherDatei-Inhalte: lokale Platte / Hetzner Volume / S3-Objektspeicher

1 Server dimensionieren

NutzungServerSpeicher
1–2 Personen, Dateien + KalenderCPX22 (2/4)lokale Platte, ~40 GB (Gigabyte) reichen fangs
Haushalt (3–5), Fotos, gelegentlich OfficeCPX32 (4/8)Hetzner Volume (live vergrößerbar) oder S3
+ Collabora dauerhaft, viele FotosCPX42 (8/16)S3-Objektspeicher als Primärspeicher („unbegrenzt“)
RAM ist der Engpass

php-fpm + MariaDB + Redis + (Collabora) laufen alle gleichzeitig. CPX22 mit 4 GB geht für Dateien, wird mit Collabora eng. Fotos-Vorschauen und der Cron-Job fressen zusätzlich CPU (Central Processing Unit) – darum die neue AMD-Linie (cpx), die bei Hetzner besser verfügbar ist als cx.

Grundserver nach der Mehrere-Dienste-Anleitung (Traefik ist schon da). Nextcloud kommt als weiterer Stack unter /opt/stacks/nextcloud.

2 DNS, Traefik, .well-known

Das generische Compose-/Label-Muster (Netz proxy, secure-headers, ein Router je Dienst) steht als Baustein in Mehrere Dienste auf einem Server. Hier nur der Nextcloud-Zusatz: die .well-known-Redirects und HSTS.

  1. Cloudflare-DNS: cloud als A/AAAA (IPv6-Adress-Record) auf den Server (oder CNAME auf den Tunnel).
  2. Traefik-Labels am Nextcloud-Service (Compose unten). Wichtig: die .well-known-Weiterleitungen – sonst meckert der Admin-Bereich und CalDAV-Autoconfig scheitert.
Labels am nginx-Service (Traefik v3)
labels:
  - traefik.enable=true
  - traefik.http.routers.nc.rule=Host(`cloud.seb4u.com`)
  - traefik.http.routers.nc.entrypoints=websecure
  - traefik.http.routers.nc.tls.certresolver=le
  - traefik.http.routers.nc.middlewares=nc-wellknown,nc-headers
  - traefik.http.services.nc.loadbalancer.server.port=80
  # .well-known -> remote.php/dav
  - traefik.http.middlewares.nc-wellknown.redirectregex.regex=^https://([^/]+)/\.well-known/(card|cal)dav
  - traefik.http.middlewares.nc-wellknown.redirectregex.replacement=https://$${1}/remote.php/dav/
  - traefik.http.middlewares.nc-wellknown.redirectregex.permanent=true
  # HSTS
  - traefik.http.middlewares.nc-headers.headers.stsSeconds=15552000
  - traefik.http.middlewares.nc-headers.headers.stsIncludeSubdomains=true

3 Compose-Stack

Datei · /opt/stacks/nextcloud/compose.yaml
services:
  db:
    image: mariadb:11.4
    restart: unless-stopped
    command: --transaction-isolation=READ-COMMITTED --log-bin=binlog --binlog-format=ROW
    environment:
      MARIADB_AUTO_UPGRADE: "1"
      MYSQL_DATABASE: nextcloud
      MYSQL_USER: nextcloud
      MYSQL_PASSWORD_FILE: /run/secrets/db
      MYSQL_RANDOM_ROOT_PASSWORD: "yes"
    volumes: [ db:/var/lib/mysql ]
    secrets: [ db ]
    networks: [ internal ]

  redis:
    image: redis:7-alpine
    restart: unless-stopped
    command: redis-server --save "" --maxmemory 256mb --maxmemory-policy allkeys-lru
    networks: [ internal ]

  app:
    image: nextcloud:30-fpm-alpine
    restart: unless-stopped
    environment:
      MYSQL_HOST: db
      MYSQL_DATABASE: nextcloud
      MYSQL_USER: nextcloud
      MYSQL_PASSWORD_FILE: /run/secrets/db
      REDIS_HOST: redis
      NEXTCLOUD_TRUSTED_DOMAINS: cloud.seb4u.com
      NEXTCLOUD_ADMIN_USER: admin
      NEXTCLOUD_ADMIN_PASSWORD_FILE: /run/secrets/nc_admin
      OVERWRITEPROTOCOL: https
      OVERWRITECLIURL: https://cloud.seb4u.com
      PHP_MEMORY_LIMIT: 1G
      PHP_UPLOAD_LIMIT: 10G
    volumes:
      - nc:/var/www/html
      - data:/var/www/html/data
    secrets: [ db, nc_admin ]
    networks: [ internal ]

  cron:
    image: nextcloud:30-fpm-alpine
    restart: unless-stopped
    entrypoint: /cron.sh                # <-- DER Cron-Job, siehe Abschnitt 5
    volumes:
      - nc:/var/www/html
      - data:/var/www/html/data
    networks: [ internal ]
    depends_on: [ app ]

  web:
    image: nginx:1.27-alpine
    restart: unless-stopped
    volumes:
      - nc:/var/www/html:ro
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
    networks: [ internal, web ]
    depends_on: [ app ]
    # hier die Traefik-Labels aus Abschnitt 2

volumes: { db: {}, nc: {}, data: {} }
networks:
  web: { name: web, external: true }
  internal: {}
secrets:
  db:       { file: ./db_password.txt }
  nc_admin: { file: ./admin_password.txt }

nginx.conf für Nextcloud-fpm: die offizielle Vorlage aus dem nextcloud/docker-Repo (.examples/dockerfiles/fpm/nginx/nginx.conf) – sie enthält die client_max_body_size, die fastcgi_*-Timeouts und die Regeln, die Nextcloud im Sicherheits-Scan erwartet.

4 Erst-Einrichtung

Server
cd /opt/stacks/nextcloud
openssl rand -base64 24 > db_password.txt
openssl rand -base64 24 > admin_password.txt   # danach in den Passwortmanager!
docker compose up -d
docker compose logs -f app                      # "Initializing finished" abwarten
Server · occ (Nextclouds CLI)
occ() { docker compose exec -u www-data app php occ "$@"; }

occ config:system:set default_phone_region --value DE
occ config:system:set maintenance_window_start --type integer --value 1   # 01:00 UTC fuer schwere Jobs
occ config:system:set memcache.local     --value '\OC\Memcache\APCu'
occ config:system:set memcache.distributed --value '\OC\Memcache\Redis'
occ config:system:set memcache.locking   --value '\OC\Memcache\Redis'
occ config:system:set redis host --value redis
occ config:system:set redis port --type integer --value 6379

occ db:add-missing-indices
occ maintenance:repair --include-expensive
occ status
Was hier passiert

Die erste Zeile ist der Schlüssel zum Rest – ohne sie müsste vor jedem Aufruf der ganze docker-Vorspann stehen:

KonstruktStellebewirkt
occ() { docker compose exec -u www-data app php occ "$@"; } Zeile 1 Eine Shell-Funktion – eine Abkürzung, die nur in dieser Konsolensitzung existiert. Alles danach liest sich, als wäre occ ein installiertes Programm, tatsächlich wird jedes Mal der lange Aufruf ausgeführt. Sie muss mitkopiert werden, sonst scheitern alle folgenden Zeilen mit occ: command not found. Nach dem Schließen des Fensters ist sie weg; für dauerhaft gehört sie in die ~/.bashrc.
"$@" in der Funktion Reicht alle übergebenen Argumente einzeln weiter – und zwar unter Beibehaltung ihrer Grenzen. Mit $* oder ohne Anführungszeichen würde ein Argument mit Leerzeichen in mehrere zerfallen; bei --value '\OC\Memcache\APCu' weiter unten macht genau das den Unterschied.
docker compose exec -u www-data in der Funktion Führt den Befehl als der Benutzer aus, dem die Nextcloud-Dateien gehören. Als root ausgeführt legt occ Dateien an, die dem Webserver anschließend nicht mehr gehören – ein Fehler, der erst Tage später als unerklärliche Rechteprobleme auffällt. app ist dabei der Dienstname aus der compose-Datei, nicht der Containername.
--type integer zwei Aufrufe occ speichert Werte sonst als Zeichenkette. Nextcloud erwartet bei maintenance_window_start und dem Redis-Port aber eine Zahl und ignoriert "1" stillschweigend. Ein Blick in config/config.php zeigt den Unterschied: '1' gegen 1.
'\OC\Memcache\APCu' memcache Die einfachen Anführungszeichen sind Pflicht: die Backslashes sind Teil des PHP-Klassennamens. In doppelten Anführungszeichen würde die Shell sie als Maskierungszeichen deuten und der Wert käme verstümmelt an.
memcache.local vs. memcache.distributed vs. memcache.locking memcache Drei verschiedene Aufgaben, deshalb drei Zeilen. local ist der Cache dieses Servers (APCu, im PHP-Prozess). distributed teilen sich mehrere Server – hier Redis. locking verhindert, dass zwei Zugriffe dieselbe Datei gleichzeitig ändern; ohne diese dritte Zeile treten bei parallelen Uploads sporadisch beschädigte Dateien auf.
occ redis host --value redis Redis redis ist hier der Dienstname aus der compose-Datei, den Docker im internen Netz als Hostnamen auflöst – kein DNS-Eintrag und keine IP nötig.
occ db:add-missing-indices Aufräumen Legt Datenbank-Indizes an, die neuere Nextcloud-Versionen erwarten, bei einem Update aber nicht automatisch ergänzen – die Sperre während des Anlegens könnte bei großen Installationen zu lange dauern. Genau darauf weist die Weboberfläche unter „Sicherheits- & Einrichtungswarnungen“ hin.
maintenance:repair --include-expensive Aufräumen Führt zusätzlich die Reparaturschritte aus, die länger dauern und deshalb bei jedem Update übersprungen werden. Bei einer frischen Installation sind sie in Sekunden durch – später mit vielen Dateien können daraus Minuten werden, in denen die Instanz nicht erreichbar ist.
Sicherheits-Scan

Nach dem ersten Login: scan.nextcloud.comcloud.seb4u.com eingeben. Ziel: Note A. Findet fehlende Header, .well-known-Probleme, veraltete Versionen.

5 Der Cron-Job – die klassische Falle

Ohne einen echten Cron-Job läuft Nextcloud scheinbar, aber: Erinnerungen kommen nicht, Vorschaubilder fehlen, der Papierkorb läuft voll, die DB wächst, Foto-Erkennung passiert nie. Der Admin-Bereich zeigt eine gelbe Warnung.

  1. Der cron-Container oben ruft /cron.sh auf – das startet busybox crond, das alle 5 Minuten php -f cron.php ausführt.
  2. In Nextcloud: Verwaltung Grundeinstellungen Hintergrund-Aufgaben → auf Cron stellen (nicht „AJAX“, nicht „Webcron“).
  3. Prüfen: occ background:cron und im Admin-Bereich steht „Zuletzt ausgeführt: vor wenigen Minuten“.
Server · Test
docker compose exec -u www-data app php cron.php     # manueller Lauf, muss ohne Fehler durch
docker compose logs cron --tail 20
maintenance_window_start

Ohne dieses Fenster laufen die schweren wöchentlichen Jobs (Datei-Scans, Aktivitäts-Aufräumen) irgendwann tagsüber und machen die Instanz zäh. Auf eine ruhige Nachtstunde (UTC!) setzen – siehe occ-Befehl oben.

6 Wo die Dateien liegen

Zielwiegut / schlecht
Lokale Server-Plattedas data-Volumeschnell, einfach – aber begrenzt (Server-Disk) und beim Server-Neuaufbau mitzunehmen
Hetzner VolumeVolume anhängen, data darauf mountenlive vergrößerbar (bis 10 TB), überlebt den Server, ~0,044 €/GB/Mon
S3-Objektspeicher als Primärspeicherobjectstore in config.phpvor der Ersteinrichtung„unbegrenzt“, günstig pro GB – aber jede Datei-Operation ist ein S3-Request, Metadaten bleiben in der DB
Storage Box als externer SpeicherSMB (Server Message Block)/WebDAV (Web Distributed Authoring and Versioning) mounten → App „Externe Speicher“gut für Kalt-Daten / große Medien, nicht für den Haupt-Sync-Pfad
config.php – S3 als Primary Storage (VOR dem ersten Setup setzen)
'objectstore' => [
  'class' => '\\OC\\Files\\ObjectStore\\S3',
  'arguments' => [
    'bucket'      => 'nextcloud',
    'autocreate'  => true,
    'key'         => getenv('OBJECTSTORE_S3_KEY'),
    'secret'      => getenv('OBJECTSTORE_S3_SECRET'),
    'hostname'    => 'fsn1.your-objectstorage.com',   # Hetzner Object Storage
    'port'        => 443,
    'use_ssl'     => true,
    'use_path_style' => true,
    'region'      => 'fsn1',
  ],
],
Nachträglich wechseln ist schwer

Von lokaler Platte auf S3 (oder umgekehrt) migriert man nicht einfach – es gibt Werkzeuge, aber sie sind fehleranfällig. Entscheide vorher. Im Zweifel: mit dem Volume anfangen, das wächst mit.

7 Redis & PHP-Tuning

  • APCu lokal + Redis verteilt (im occ-Block oben gesetzt): APCu für den schnellen Prozess-Cache, Redis für das File-Locking – das verhindert, dass zwei Sync-Clients dieselbe Datei zerschießen.
  • PHP_MEMORY_LIMIT: 1G (im Compose) – darunter brechen große Foto-Alben und der ZIP-Download von Ordnern ab.
  • PHP_UPLOAD_LIMIT + nginx client_max_body_size müssen beide groß sein, sonst scheitert der Upload großer Dateien am kleineren Wert.
  • php-fpm pm.max_children: Faustregel (RAM_fuer_fpm / 80 MB). Zu hoch → OOM (Out Of Memory), zu niedrig → hängende Requests unter Last. Env PHP_FPM_PM_MAX_CHILDREN im Image.
  • Nach Config-Änderungen docker compose restart app web und occ maintenance:update:htaccess.

8 Office: Collabora

Dokumente im Browser bearbeiten. Zwei Wege:

  • Eingebautes „Nextcloud Office“ (App Nextcloud Office + built-in CODE server): ein Klick, kein extra Container – aber der CODE-Server läuft im Nextcloud-Container und braucht dort ~1 GB extra. Für < ~10 Nutzer ok.
  • Eigener Collabora-Container (empfohlen ab Haushaltsgröße):
Zusatz zu compose.yaml
  collabora:
    image: collabora/code:24.04
    restart: unless-stopped
    environment:
      aliasgroup1: https://cloud.seb4u.com:443
      extra_params: --o:ssl.enable=false --o:ssl.termination=true
    networks: [ web ]
    # Traefik: Host(`office.seb4u.com`) -> port 9980

In Nextcloud: App Nextcloud Office installieren → Verwaltung Office → „Eigener Server“ → https://office.seb4u.com. Collabora braucht 1–2 GB RAM – das ist der Grund für CPX42 statt CPX32, wenn Office dauerhaft laufen soll.

9 Clients & Sync

  • Desktop: Nextcloud-Client (Windows/Mac/Linux) – Ordner-Sync, Virtual Files (nur bei Bedarf herunterladen).
  • Mobil: Nextcloud-App (Foto-Auto-Upload!), plus separate Apps für Notes, Deck, Talk.
  • Kalender/Kontakte iOS: Einstellungen → Kalender → Account → CalDAV, Server cloud.seb4u.com (dank .well-known reicht die Domain), App-Passwort aus Nextcloud (Einstellungen Sicherheit App-Passwort).
  • Android: DAVx&sup5; für CalDAV/CardDAV (vCard Extensions to WebDAV).
  • Immer App-Passwörter pro Gerät, nie das Hauptpasswort – so lässt sich ein verlorenes Gerät einzeln sperren.

10 Backup & Restore

Drei Teile: die Datenbank, das data-Verzeichnis (bzw. der S3-Bucket) und config.php. Alle drei müssen zueinander passen – darum kurz in den Wartungsmodus.

im Backup-Skript (siehe Backup-Anleitung), vor "restic backup"
cd /opt/stacks/nextcloud
docker compose exec -T -u www-data app php occ maintenance:mode --on
docker compose exec -T db mariadb-dump --single-transaction nextcloud > dumps/nextcloud.sql
# restic sichert jetzt: dumps/, das data-Volume, config.php  (siehe Backup-Anleitung)
docker compose exec -T -u www-data app php occ maintenance:mode --off
  • Bei S3-Primärspeicher liegen die Datei-Inhalte im Bucket – den separat versionieren (Bucket-Versioning + restic/rclone auf einen zweiten Bucket). Die DB bleibt trotzdem kritisch: ohne sie sind die S3-Objekte unentschlüsselbarer Datensalat.
  • Restore-Probe: DB einspielen, data zurücklegen, config.php passend, occ maintenance:data-fingerprint, occ files:scan --all, maintenance:mode --off. Login testen, eine Datei öffnen. – Details im Backup & Restore-Guide.
  • Nextcloud AIO bringt ein eigenes BorgBackup mit – wenn du AIO nutzt, das nehmen und trotzdem einmal einen Restore proben.

11 Betrieb & Updates

  • Eine Hauptversion pro Schritt: 30 → 31 → 32, nie 30 → 32. Image-Tag im Compose um eins hochsetzen, docker compose up -d, das Image migriert beim Start (bzw. occ upgrade). Vorher Backup.
  • occ status, occ app:update --all, occ integrity:check-core, occ maintenance:repair nach jedem Update.
  • Apps sparsam: jede installierte App ist Angriffsfläche und ein potenzielles Update-Hindernis. Nur was du wirklich nutzt.
  • Monitoring: die /ocs/v2.php/apps/serverinfo/api/v1/info (mit Token) liefert JSON (JavaScript Object Notation) für Uptime Kuma / Netdata, oder simpel ein HTTP-Keyword-Check auf die Login-Seite.
  • Log: occ log:manage --level warning (nicht debug im Betrieb), docker compose logs app | grep -i error.
  • Benachrichtigungs-E-Mail: Port 25 ist bei Hetzner gesperrt – einen SMTP-Relay eintragen (Brevo/Mailgun Free-Tier oder eigener Mailserver).

+ Hetzner-spezifisch

  • Volume für data: in der Console anhängen, formatieren, nach /mnt/nc-data mounten (fstab!), im Compose data darauf zeigen lassen. Vergrößern: Console → Volume → Größe → danach resize2fs.
  • Hetzner Object Storage (S3) für Primärspeicher: Buckets + Zugangsschlüssel in der Console, Endpoint <location>.your-objectstorage.com, use_path_style => true.
  • Storage Box als externer „Kalt“-Speicher: als SMB einbinden oder in Nextcloud die App External storage mit WebDAV (https://<user>.your-storagebox.de).
  • Server-Neuaufbau: die Volumes (db, nc, data) überleben den Snapshot-Zyklus nicht automatisch – entweder Hetzner-Volume nutzen (bleibt) oder die Volumes ins Backup und beim Aufbau zurück.

+ Kosten

Preise sind Richtwerte. Hetzner hat 2026 zweimal erhöht (zuletzt 15. Juni: CX/CAX +30–40 %, CPX/CCX über 100 %). Aktuelle Server-Preise, alle Nebenposten, ein Rechner und Beispielrechnungen stehen zentral in Kosten & Budget.

Posten€/Tag€/Monat
Server CPX32 (4 vCPU / 8 GB) – CX33/CAX21 ~8,5–10,5 falls frei~1,17~35,5
Primary-IP (IPv4)~0,02~0,50
Hetzner Volume 100 GB (für data)~0,15~4,40
ODER Object Storage (S3), ~200 GB~0,04~1–2
Collabora dauerhaft → CPX42 statt CPX32+~1,11+~34
Backup (Restic → Storage Box, siehe eigene Anleitung)~0,13~3,8
Haushalt, ohne Office~0,60~16–18

Vergleich: ein 2-TB-Cloud-Abo (Google One, iCloud+, Proton) liegt bei ~10 €/Monat – Nextcloud lohnt sich weniger über den Preis als über Kontrolle, Datenschutz und die Nebenfeatures (Kalender, Freigaben, Office, Apps).

⌂ Cockpit