Nextcloud su VPS con Docker, TLS e backup
Configura Nextcloud su VPS con Docker Compose, Postgres, Redis e proxy TLS. Include backup ripristinabili e upgrade sicuri per proteggere davvero i tuoi dati.
Cosa stai effettivamente configurando
Questa guida esegue Nextcloud su un VPS con Docker Compose, configura TLS tramite Let's Encrypt e imposta un backup che può essere realmente ripristinato. La configurazione usa quattro container e un proxy: l'immagine ufficiale nextcloud in ascolto sull'interfaccia di loopback, Postgres per tutti i metadati dei file, Redis per i file lock, una seconda istanza dell'immagine Nextcloud dedicata esclusivamente al ciclo cron e nginx sull'host, che termina TLS davanti a tutti questi componenti. L'installazione richiede venti minuti, ma non è questo l'aspetto più importante. Due decisioni prese nella prima ora determinano se tra un anno avrai ancora i tuoi file: usare un database reale invece di SQLite e creare un backup che acquisisca la directory dei dati, il database e config.php come un unico insieme coerente.
Questa procedura presuppone Ubuntu 24.04 LTS o Debian 13, Docker Engine con il plugin Compose v2 installato dal repository ufficiale Docker e un record DNS A (oltre a AAAA se disponi di IPv6) già configurato in modo che punti cloud.example.com al VPS. È necessario un server sotto il tuo controllo: non puoi terminare TLS ed eseguire un dump del database sul SaaS di terzi.
Dimensionamento: che cosa consuma effettivamente la memoria
L'uso della memoria di Nextcloud dipende soprattutto da tre componenti, e nessuna di queste è semplicemente "Nextcloud".
Worker PHP. L'immagine -apache gestisce ogni richiesta simultanea tramite un processo worker che contiene un interprete PHP. Ogni worker può arrivare a PHP_MEMORY_LIMIT prima che PHP termini la richiesta. La memoria residente nel caso peggiore è approssimativamente numero di richieste simultanee × limite di memoria; inoltre, un client di sincronizzazione desktop apre diverse connessioni parallele per ogni utente. È la concorrenza, non il numero di utenti, a determinare il limite massimo.
Il database. Postgres crea un processo backend per ogni connessione e mantiene residenti i buffer condivisi. Il suo working set cresce in base al numero di file, non al numero di byte: oc_filecache contiene una riga per ogni file e per ogni utente. Centomila file piccoli richiedono più risorse al database rispetto a cento file grandi.
Generazione delle anteprime. La generazione di una miniatura decodifica l'immagine sorgente in memoria alla risoluzione completa. Le anteprime video invocano ffmpeg tramite la shell. Eseguire occ preview:generate-all ripete questo picco più volte consecutivamente ed è il modo più comune per portare un VPS con poca memoria all'intervento dell'OOM killer.
Redis è relativamente leggero. Qualsiasi componente aggiunto in seguito, come Collabora, la ricerca full-text o uno scanner antivirus, è un servizio residente separato con un proprio consumo di risorse. Deve quindi essere incluso nel dimensionamento prima dell'abilitazione.
Se la RAM è limitata, puoi intervenire su questi parametri: riduci PHP_MEMORY_LIMIT, limita preview_max_x / preview_max_y / preview_max_filesize_image, restringi enabledPreviewProviders ai formati che visualizzi effettivamente e imposta trashbin_retention_obligation e versions_retention_obligation, in modo che la directory dei dati non cresca silenziosamente fino a diverse volte la dimensione dei file. Aggiungi un file di swap. Lo swap è lento e un processo terminato dall'OOM killer durante un aggiornamento causa problemi ancora più gravi.
Perché SQLite si blocca
Nextcloud supporta SQLite e l'immagine ufficiale lo utilizza senza problemi. Non usarlo. SQLite serializza le scritture tramite un lock sull'intero database: è possibile eseguire una sola scrittura alla volta sull'intero file. Nextcloud scrive continuamente dati, lock sui file, righe delle attività, voci della cache e stato dei job. Inoltre, un singolo client desktop che sincronizza un albero di directory genera molte richieste parallele. In questo scenario si verificano SQLSTATE[HY000]: General error: 5 database is locked e risposte HTTP 500. Il problema compare proprio quando l'istanza inizia a essere utilizzata concretamente.
La conversione successiva è possibile con occ db:convert-type, ma consiste in una migrazione lunga e indivisibile su un dataset attivo. Inizia con Postgres o MariaDB.
Il file Compose
Inserire questo contenuto in /srv/nextcloud/compose.yaml, con i secret in un file .env adiacente con modalità 600.
services:
db:
image: postgres:16-alpine
restart: unless-stopped
volumes:
- db:/var/lib/postgresql/data
environment:
POSTGRES_DB: nextcloud
POSTGRES_USER: nextcloud
POSTGRES_PASSWORD: ${DB_PASSWORD}
redis:
image: redis:7-alpine
restart: unless-stopped
command: redis-server --requirepass ${REDIS_PASSWORD}
app:
image: nextcloud:31-apache
restart: unless-stopped
depends_on: [db, redis]
ports:
- "127.0.0.1:8080:80"
volumes:
- html:/var/www/html
- /srv/nextcloud/data:/var/www/html/data
environment:
POSTGRES_HOST: db
POSTGRES_DB: nextcloud
POSTGRES_USER: nextcloud
POSTGRES_PASSWORD: ${DB_PASSWORD}
REDIS_HOST: redis
REDIS_HOST_PASSWORD: ${REDIS_PASSWORD}
NEXTCLOUD_ADMIN_USER: admin
NEXTCLOUD_ADMIN_PASSWORD: ${ADMIN_PASSWORD}
NEXTCLOUD_TRUSTED_DOMAINS: cloud.example.com
TRUSTED_PROXIES: 172.16.0.0/12
OVERWRITEPROTOCOL: https
OVERWRITECLIURL: https://cloud.example.com
APACHE_DISABLE_REWRITE_IP: "1"
PHP_MEMORY_LIMIT: 512M
PHP_UPLOAD_LIMIT: 10G
cron:
image: nextcloud:31-apache
restart: unless-stopped
entrypoint: /cron.sh
depends_on: [db, redis]
volumes:
- html:/var/www/html
- /srv/nextcloud/data:/var/www/html/data
volumes:
db:
html:Bloccare il tag principale e verificare quello attuale su Docker Hub prima di copiare 31 alla lettera. In futuro latest potrebbe portare automaticamente a una versione principale diversa con alcuni docker compose pull, ma Nextcloud non supporta questo aggiornamento.
La directory dei dati è un bind mount, non un volume denominato, per scelta: un percorso che può essere indicato direttamente a uno strumento di backup è più utile di una struttura ordinata. Crearla con l'UID www-data dell'immagine e con i permessi richiesti da Nextcloud:
sudo mkdir -p /srv/nextcloud/data
sudo chown -R 33:33 /srv/nextcloud/data
sudo chmod 0770 /srv/nextcloud/dataNotare la pubblicazione della porta: 127.0.0.1:8080:80. Docker pubblica le porte scrivendo regole DNAT che vengono valutate prima che il pacchetto raggiunga la catena INPUT di ufw; un semplice 8080:80 espone un'istanza Nextcloud non cifrata su Internet, indipendentemente dalla configurazione di ufw. Il binding al loopback mantiene il servizio fuori dall'interfaccia pubblica. In questo modo il firewall deve consentire soltanto il proxy. Se invece si preferisce non lasciare SSH aperto a tutta Internet, raggiungere il VPS tramite una VPN WireGuard self-hosted consente di rimuovere completamente la porta 22 dalle regole pubbliche:
sudo ufw allow 22/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enableAvviarlo con docker compose up -d, quindi monitorare docker compose logs -f app. Al primo avvio, il container copia l'intero albero dell'applicazione nel volume ed esegue l'installer; il container non risponde finché l'operazione non è terminata.
TLS e il reverse proxy
Installa nginx e certbot dai repository della distribuzione, crea un server block semplice sulla porta 80 con il corretto server_name, quindi lascia che certbot lo riscriva. Il funzionamento della challenge HTTP-01, il timer di rinnovo e gli scenari di errore sono descritti integralmente in rilascio di certificati Let's Encrypt con certbot e nginx su Ubuntu 24.04:
sudo apt install nginx certbot python3-certbot-nginx
sudo certbot --nginx -d cloud.example.comCertbot aggiunge le direttive ssl_certificate e il redirect da :80 a :443, quindi installa un timer systemd che rinnova il certificato ogni 90 giorni. Verifica che esista con systemctl list-timers | grep certbot: un timer di rinnovo mai abilitato equivale a una scadenza certa dopo 90 giorni.
Il blocco proxy:
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name cloud.example.com;
# certbot manages ssl_certificate / ssl_certificate_key here
add_header Strict-Transport-Security "max-age=15552000; includeSubDomains" always;
client_max_body_size 10G;
client_body_timeout 300s;
location = /.well-known/carddav { return 301 /remote.php/dav; }
location = /.well-known/caldav { return 301 /remote.php/dav; }
location / {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
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;
proxy_request_buffering off;
proxy_buffering off;
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
}
}Con nginx 1.25 e versioni successive, aggiungi http2 on;. Ubuntu 24.04 distribuisce una build precedente, in cui l'equivalente è listen 443 ssl http2;. nginx -t indica quale delle due direttive è accettata dalla build installata.
client_max_body_size e i timeout di lettura elevati impediscono che gli upload di grandi dimensioni si interrompano a metà. proxy_request_buffering off inoltra l'upload in streaming, senza memorizzare prima l'intero file sul disco del proxy.
nginx sull'host è la soluzione più semplice per una singola applicazione. Se Nextcloud deve condividere il VPS con altri container, eseguire Traefik come reverse proxy Docker Compose per più applicazioni sposta il routing e il rilascio dei certificati nelle label dei container. Le stesse problematiche relative a client_max_body_size e ai timeout si ripresentano sotto forma di impostazioni di middleware e trasporto.
trusted_proxies e overwriteprotocol
È qui che la maggior parte delle istanze Nextcloud self-hosted presenta problemi. I sintomi sembrano non avere relazione con la causa.
X-Forwarded-Proto: https viene applicato soltanto quando la richiesta arriva da un indirizzo elencato in trusted_proxies. Se non viene applicato, Nextcloud considera la richiesta come HTTP semplice e genera URL http://. Il proxy li reindirizza a HTTPS. Il browser segue il reindirizzamento e Nextcloud genera di nuovo http://. Questo è il loop di reindirizzamento. OVERWRITEPROTOCOL: https forza lo schema in ogni caso.
Il problema di TRUSTED_PROXIES è che l'indirizzo visualizzato da Nextcloud non è 127.0.0.1. nginx viene eseguito sull'host e si connette a una porta pubblicata. Di conseguenza, il container vede il gateway del bridge Docker, un indirizzo appartenente a 172.x. Individuate la subnet effettiva:
docker network inspect nextcloud_default \
-f '{{range .IPAM.Config}}{{.Subnet}}{{end}}'Inserite quel CIDR, oppure il 172.16.0.0/12 che lo comprende, in TRUSTED_PROXIES. Se l'intervallo è troppo ampio, qualsiasi client potrebbe falsificare X-Forwarded-For. Se è errato, ogni accesso risulterà provenire dall'indirizzo del gateway, la protezione contro i tentativi di forza bruta bloccherà l'intera istanza contemporaneamente e la panoramica dell'amministrazione mostrerà "La configurazione dell'header del reverse proxy non è corretta oppure state accedendo a Nextcloud da un proxy attendibile."
OVERWRITECLIURL è importante per il container cron, che non riceve richieste in ingresso da cui ricavare il nome host. Senza questa impostazione, i job in background generano link a localhost e le notifiche email contengono URL inutilizzabili.
Processi in background: cron, non AJAX
Il job runner predefinito di Nextcloud è AJAX: i job vengono eseguiti come effetto collaterale del caricamento di una pagina. Alle 04:00 nessuno naviga, quindi la scadenza del cestino, la pulizia delle versioni, la generazione delle anteprime e i nuovi tentativi delle operazioni federate si bloccano. Il primo sintomo è una directory dei dati che continua a crescere. Il servizio cron descritto sopra esegue il ciclo ufficiale di /cron.sh sugli stessi volumi. Configura Nextcloud perché lo utilizzi:
docker compose exec -u www-data app php occ background:cronOgni comando occ segue questo formato: docker compose exec -u www-data app php occ <command>. Conviene definire un alias.
Backup: tre elementi, oppure nessuno
Un backup del solo filesystem ripristina un'istanza non funzionante. La directory dei dati contiene i byte; Postgres contiene la cache dei file, le condivisioni, gli utenti e lo stato dell'applicazione; config.php contiene le credenziali del database, l'ID dell'istanza e il salt delle password. Se ripristini i file senza il database, Nextcloud non può visualizzarli. Se ripristini il database senza config.php, non può aprire il database. Se ripristini un database precedente su una directory dei dati più recente, ottieni condivisioni che puntano a file spostati.
Esegui il backup di tutti e tre i componenti, con l'istanza in stato quiescente:
#!/usr/bin/env bash
set -euo pipefail
cd /srv/nextcloud
DEST="/var/backups/nextcloud/$(date -u +%Y%m%dT%H%M%SZ)"
mkdir -p "$DEST"
occ() { docker compose exec -T -u www-data app php occ "$@"; }
occ maintenance:mode --on
trap 'occ maintenance:mode --off' EXIT
docker compose exec -T db \
pg_dump -U nextcloud --clean --if-exists nextcloud | gzip > "$DEST/db.sql.gz"
docker compose exec -T app \
tar -C /var/www/html -cf - config custom_apps themes > "$DEST/app.tar"
rsync -a --delete /srv/nextcloud/data/ /var/backups/nextcloud/data/La modalità di manutenzione fa sì che il dump e la copia dei file siano coerenti tra loro. Se la ometti, prima o poi acquisirai un database che fa riferimento a un file che rsync non aveva ancora raggiunto. Lo script conserva i dump del database con timestamp, ma mantiene una sola copia mirror rotativa della directory dei dati: rsync --delete la sovrascrive a ogni esecuzione. Di conseguenza, solo il dump più recente corrisponde alla copia dei file.
Poi trasferisci il backup fuori dal server. Un backup che si trova sullo stesso VPS dei dati protetti è una copia, non un backup. restic verso l'object storage o un secondo host è la soluzione abituale; la deduplicazione gestisce la directory dei dati molto meglio di un archivio tar notturno. La procedura completa, dall'inizializzazione del repository al timer notturno e alla prova di ripristino, è descritta in backup di VPS fuori dal server con restic.
Il ripristino non consiste semplicemente nell'eseguire i passaggi al contrario. Uno stack avviato da zero esegue l'installer e scrive un nuovo config.php, un nuovo ID dell'istanza e un nuovo salt delle password. Importare il dump sopra questa nuova identità lascia sessioni e token delle condivisioni non funzionanti. Ripristina prima la vecchia identità, in questo ordine:
docker compose up -d && docker compose stop app cron # create the volumes, then halt the app
sudo rsync -a --delete /var/backups/nextcloud/data/ /srv/nextcloud/data/
docker compose run --rm -T --entrypoint "" app \
tar -C /var/www/html -xf - < app.tar # the original config.php returns
gunzip -c db.sql.gz | docker compose exec -T db psql -U nextcloud -d nextcloud
docker compose start app cron
docker compose exec -T -u www-data app php occ maintenance:mode --off
docker compose exec -T -u www-data app php occ files:scan --allfiles:scan riconcilia la cache dei file con ciò che è effettivamente presente sul disco. Esegui questa procedura una volta su un VPS di prova, prima di averne bisogno. La stessa separazione tra byte su disco e metadati in Postgres vale per tutte le altre applicazioni con questa struttura. Per questo motivo, un backup di Immich che acquisisce la libreria ma non il database ripristina una timeline vuota.
Aggiornamenti: una versione principale alla volta
Nextcloud supporta l’aggiornamento di una sola versione principale alla volta. Il passaggio dalla 29 alla 31 non termina correttamente: l’aggiornamento fallisce con Exception: Updates between multiple major versions and downgrades are unsupported. e lascia il sistema in modalità di manutenzione.
Per aggiornare il deployment Docker, esegui un backup, modifica il tag da 31 a 32 nei servizi app e cron, quindi esegui docker compose pull && docker compose up -d e poi docker compose logs -f app. L’entrypoint dell’immagine rileva il codice più recente rispetto ai dati esistenti ed esegue autonomamente occ upgrade. Non interrompere l’operazione. Quando nei log non compaiono più messaggi, esegui docker compose exec -u www-data app php occ status e verifica versionstring e che le app siano nuovamente abilitate.
Due regole evitano problemi: aggiorna una versione principale, verifica il risultato, quindi aggiorna quella successiva. Inoltre, non modificare mai il tag del servizio app senza modificare anche cron nello stesso modo: usare due versioni diverse di Nextcloud con un unico database può causare la corruzione dei dati.
Gli errori che vedrai effettivamente
"Your data directory is readable by other users. Please change the permissions to 0770." La directory montata tramite bind ha i bit di lettura impostati per il gruppo o per tutti gli utenti. sudo chmod 0770 /srv/nextcloud/data e sudo chown -R 33:33 /srv/nextcloud/data.
"Your data directory is invalid. Ensure there is a file called .ocdata in the root." Il bind mount punta a una directory che Nextcloud non ha mai inizializzato, il percorso contiene un errore di digitazione oppure una directory vuota appena creata è stata montata al posto di un'istanza funzionante. Verifica che il percorso sull'host corrisponda alla riga del volume.
"Access through untrusted domain." Il nome host della richiesta non è presente in trusted_domains. NEXTCLOUD_TRUSTED_DOMAINS si applica soltanto durante la prima installazione; in seguito impostalo mentre il servizio è in esecuzione: occ config:system:set trusted_domains 1 --value=cloud.example.com.
502 Bad Gateway, con connect() failed (111: Connection refused) while connecting to upstream in /var/log/nginx/error.log. nginx non ha raggiunto alcun servizio su 127.0.0.1:8080. Il container potrebbe essere ancora in fase di inizializzazione (controlla docker compose logs app), potrebbe essere terminato (docker compose ps) oppure la riga di pubblicazione potrebbe non corrispondere alla porta proxy_pass. Conferma con ss -ltnp | grep 8080.
Un ciclo di redirect o avvisi "insecure" nella panoramica dell'amministrazione. Manca OVERWRITEPROTOCOL: https oppure TRUSTED_PROXIES non contiene la subnet del gateway Docker. Consulta la sezione precedente sul proxy.
LockedException: "files/..." is locked. Con REDIS_HOST impostato, l'immagine configura Redis come backend per il locking e i lock obsoleti sono rari. Senza questa impostazione, i lock vengono memorizzati nella tabella del database oc_file_locks e una richiesta terminata durante una scrittura lascia righe residue. Verifica che Redis sia effettivamente in uso: occ config:system:get memcache.locking dovrebbe restituire la classe Redis, prima di eliminare manualmente le righe dei lock.
"The PHP memory limit is below the recommended value of 512MB." Aumenta PHP_MEMORY_LIMIT e ricrea il container. Considera l'effetto di questa modifica sul limite massimo nei casi peggiori.
Cosa si rompe su larga scala
Il primo limite si raggiunge quando la directory dei dati supera le dimensioni del volume. L'espansione di un volume su un VPS richiede il ridimensionamento del volume e l'espansione del filesystem. È molto meno problematico eseguire questa operazione in modo pianificato, prima che il volume raggiunga il 100%. Configura subito gli alert sull'utilizzo del disco.
Il secondo limite è oc_filecache. Gli elenchi dei file e le scansioni per la sincronizzazione diventano più lente all'aumentare del numero di righe. La soluzione richiede interventi sul database: mantieni Postgres su storage veloce, assegnagli memoria condivisa sufficiente ed elimina cestino e versioni tramite le impostazioni di conservazione, invece di lasciarli accumulare indefinitamente.
Il terzo limite è la generazione delle anteprime, che compete per le risorse con tutti gli altri servizi. Su una macchina di dimensioni ridotte, limita i provider delle anteprime e non eseguire mai occ preview:generate-all durante l'orario di lavoro. Se la maggior parte dei dati archiviati proviene dal rullino fotografico di un telefono, questa elaborazione delle miniature dovrebbe essere eseguita da un server fotografico dedicato. Confronto tra PhotoPrism e Immich per memoria RAM, app per telefono e comandi di backup descrive il costo di entrambe le soluzioni accanto a un'istanza Nextcloud.
Oltre questo livello, la risposta corretta è che i componenti aggiuntivi richiedono una macchina dedicata. Collabora e la ricerca full-text sono servizi residenti separati, con profili di utilizzo della memoria propri. Eseguirli sulla macchina che contiene anche l'unica copia dei file amplia il dominio di guasto senza offrire vantaggi. Se vuoi aggiungere la modifica dei documenti nel browser, i requisiti minimi di RAM e i limiti di connessione del vendor che distinguono OnlyOffice da Collabora determinano quale dei due possa essere eseguito su un VPS da 2 a 4 GB. Sposta lo storage dei file su uno storage primario compatibile con S3 quando il volume non è più adatto alle dimensioni richieste. Tieni presente che questo rende i backup più difficili, non più semplici: il database conserva ancora i metadati e deve essere esportato insieme al bucket.
Quando l'istanza serve utenti reali, installa Uptime Kuma davanti a essa, così riceverai una notifica del downtime prima dei client di sincronizzazione. Un cloud privato si integra bene con il tuo server di posta, mentre, se preferisci non collegare i servizi manualmente, Cloudron, CasaOS e Coolify confronta le piattaforme che automatizzano questa attività. Se il prossimo componente dell'elenco è un motore di ricerca self-hosted, aspettati un problema di natura diversa da quelli descritti sopra: gli errori 429 di SearXNG sono causati dal suo rate limiter oppure dai motori upstream che bloccano l'indirizzo IP del VPS. Solo il log permette di stabilire quale delle due cause sia responsabile.
FAQ
Posso eseguire Nextcloud su SQLite invece che su Postgres?
Sì, l'immagine ufficiale lo consente, ma un singolo client di sincronizzazione desktop che invia richieste in parallelo raggiungerà SQLSTATE[HY000]: General error: 5 database is locked e genererà errori HTTP 500. SQLite acquisisce un lock di scrittura sull'intero database, mentre Nextcloud esegue scritture continue per i lock dei file, le righe delle attività e lo stato dei job. È preferibile iniziare con Postgres o MariaDB; occ db:convert-type esiste, ma si tratta di una migrazione lunga e indivisibile sui dati in produzione.
Quanta RAM richiede realmente un VPS Nextcloud?
Dimensiona il sistema in base alla concorrenza, non al numero di utenti. La memoria residente nel caso peggiore è approssimativamente pari al numero di richieste simultanee moltiplicato per PHP_MEMORY_LIMIT, a cui vanno aggiunti i buffer condivisi di Postgres, un backend per ogni connessione e l'aumento temporaneo dovuto alla generazione delle anteprime. Un server con 2 GB può eseguire un'istanza per un piccolo ambiente domestico se limiti le anteprime e aggiungi lo swap; con Collabora o la ricerca full-text devi dimensionare un secondo insieme di servizi residenti.
Perché i caricamenti di grandi dimensioni falliscono dietro il reverse proxy nginx?
Di solito il problema dipende da due impostazioni del proxy: client_max_body_size, lasciato al valore predefinito di 1 MB, tronca la richiesta, mentre valori troppo bassi di proxy_read_timeout / proxy_send_timeout interrompono i trasferimenti lunghi a metà. Imposta entrambi su valori adeguati, configura proxy_request_buffering off per eseguire lo streaming invece dello spool e aumenta PHP_UPLOAD_LIMIT sul container dell'applicazione in modo che corrisponda.
Perché Nextcloud esegue un redirect in loop o segnala un problema relativo al reverse proxy?
Il container non vede nginx all'indirizzo 127.0.0.1, ma vede il gateway della rete bridge Docker, che si trova in 172.x. Quando quell'indirizzo manca in TRUSTED_PROXIES, l'header X-Forwarded-Proto: https viene ignorato, Nextcloud genera URL http:// e il proxy li reindirizza nuovamente. Imposta TRUSTED_PROXIES sulla subnet bridge effettiva e assegna staticamente OVERWRITEPROTOCOL: https.
Posso aggiornare Nextcloud direttamente dalla versione 29 alla 31?
No. Nextcloud supporta una sola versione principale per ogni aggiornamento. Se salti una versione, l'aggiornamento si interrompe con Updates between multiple major versions and downgrades are unsupported. e l'istanza resta in modalità manutenzione. Esegui un backup, incrementa di una versione principale il tag su entrambi i servizi app e cron, docker compose pull && docker compose up -d, verifica con occ status, quindi ripeti la procedura.