SSD Nodes Learn Hosting plans →
Руководства Matt ConnorАвтор: Matt Connor · Обновлено 2026-08-26

Установка Nextcloud на VPS с Docker Compose и TLS

Разверните Nextcloud на VPS с использованием Docker Compose, Postgres и Redis. Руководство включает настройку TLS, автоматизацию cron и надежный алгоритм резервного копирования.

Что именно вы создаете

В этом руководстве Nextcloud запускается на VPS с помощью Docker Compose, перед ним настраивается TLS через Let's Encrypt, а также создается резервная копия, которую действительно можно восстановить. Конфигурация включает четыре контейнера и прокси: официальный образ nextcloud, слушающий loopback, Postgres для хранения всех метаданных файлов, Redis для блокировок файлов, вторую копию образа Nextcloud, выполняющую только цикл cron, и nginx на хосте, который терминирует TLS перед всеми остальными компонентами. Сама установка занимает 20 минут, но это не самая важная часть. Два решения, принятые в первый час, определяют, сохранятся ли ваши файлы через год: использование полноценной базы данных вместо SQLite и резервное копирование, которое захватывает каталог данных, базу данных и config.php как единый согласованный набор.

Предполагается, что у вас установлена Ubuntu 24.04 LTS или Debian 13, Docker Engine с плагином Compose v2 из официального репозитория Docker, а DNS-запись A (плюс AAAA, если у вас есть IPv6) уже указывает cloud.example.com на ваш VPS. Для всего этого требуется сервер, который находится под вашим полным контролем; невозможно выполнить TLS termination и дамп базы данных в чужом SaaS-решении.

Планирование ресурсов: что на самом деле потребляет память

Потребление памяти Nextcloud определяется тремя факторами, и ни один из них не является «Nextcloud» в чистом виде.

PHP-воркеры. Образ -apache обслуживает каждый параллельный запрос с помощью воркер-процесса, который содержит интерпретатор PHP. Каждый воркер может вырасти до PHP_MEMORY_LIMIT, прежде чем PHP принудительно завершит запрос. Ваш худший сценарий по объему резидентной памяти — это примерно количество параллельных запросов × лимит памяти, при этом клиент синхронизации для ПК открывает несколько соединений на пользователя. Предел потребления задает именно параллельность, а не количество пользователей.

База данных. Postgres создает отдельный процесс для каждого соединения и удерживает общие буферы в оперативной памяти. Рабочий набор данных масштабируется в зависимости от количества файлов, а не объема данных: oc_filecache хранит строку для каждого файла каждого пользователя. Сто тысяч мелких файлов создают большую нагрузку на базу данных, чем сто крупных.

Генерация превью. При создании миниатюры исходное изображение декодируется в память в полном разрешении. Для превью видео вызывается ffmpeg. Запуск occ preview:generate-all приводит к многократному повторению таких скачков нагрузки, что является самой частой причиной срабатывания OOM killer на небольших VPS.

Redis потребляет сравнительно мало ресурсов. Любые дополнительные компоненты, такие как Collabora, полнотекстовый поиск или антивирусный сканер, являются отдельными резидентными сервисами со своим потреблением памяти. Их необходимо учитывать в плане ресурсов до того, как вы их включите.

Если объем оперативной памяти ограничен, используйте следующие рычаги: уменьшите PHP_MEMORY_LIMIT, ограничьте preview_max_x / preview_max_y / preview_max_filesize_image, сократите enabledPreviewProviders до форматов, которые вы действительно просматриваете, и настройте trashbin_retention_obligation и versions_retention_obligation, чтобы размер каталога данных не увеличивался незаметно в несколько раз относительно объема ваших файлов. Добавьте файл подкачки (swap). Swap работает медленно, но принудительное завершение процесса (OOM kill) в середине обновления системы — гораздо худший вариант.

Почему SQLite не подходит

Nextcloud поставляется с поддержкой SQLite, и официальный образ будет успешно использовать её по умолчанию. Не делайте этого. SQLite сериализует операции записи с помощью блокировки всей базы данных: только один процесс записи за раз для всего файла. Nextcloud постоянно выполняет запись: блокировки файлов, строки активности, записи кэша, состояние заданий, а один клиент синхронизации при обработке дерева каталогов создает множество параллельных запросов. При таком шаблоне работы возникают SQLSTATE[HY000]: General error: 5 database is locked и ошибки HTTP 500, и сбой происходит именно тогда, когда экземпляр начинает приносить реальную пользу.

Миграция на другую СУБД возможна позже с помощью occ db:convert-type, но это длительный процесс, требующий полной обработки всего набора данных без возможности частичного выполнения. Начинайте работу сразу с Postgres или MariaDB.

Файл Compose

Разместите его в /srv/nextcloud/compose.yaml, а секреты — в соседнем файле .env с правами доступа 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:

Зафиксируйте мажорную версию образа и проверьте актуальную на Docker Hub перед копированием 31. Использование latest приведет к автоматическому обновлению до новой мажорной версии при выполнении docker compose pull, что не поддерживается в Nextcloud.

Директория с данными намеренно оформлена как bind mount, а не именованный том: путь, к которому можно напрямую обратиться средствами резервного копирования, предпочтительнее чистоты структуры. Создайте её с UID www-data, который использует образ, и правами доступа, требуемыми Nextcloud:

sudo mkdir -p /srv/nextcloud/data
sudo chown -R 33:33 /srv/nextcloud/data
sudo chmod 0770 /srv/nextcloud/data

Обратите внимание на публикацию порта: 127.0.0.1:8080:80. Docker публикует порты путем создания правил DNAT, которые обрабатываются до того, как пакет попадет в цепочку INPUT брандмауэра ufw. Использование простого 8080:80 сделает Nextcloud доступным в открытом интернете без шифрования, независимо от настроек ufw. Привязка к loopback исключает сервис из публичного интерфейса. В этом случае брандмауэру достаточно разрешить доступ только для прокси. Если вы не хотите оставлять SSH открытым для всего интернета, доступ к VPS через собственный WireGuard VPN позволит полностью закрыть порт 22 в публичных правилах:

sudo ufw allow 22/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable

Запустите проект командой docker compose up -d, затем отслеживайте логи через docker compose logs -f app. При первом запуске всё дерево приложения копируется в том и выполняется установка; контейнер не будет отвечать на запросы до завершения этого процесса.

TLS и обратный прокси

Установите nginx и certbot из репозитория дистрибутива, создайте простой server block для 80 порта с правильным server_name, а затем позвольте certbot перезаписать его. Механика проверки HTTP-01, таймер обновления и режимы сбоев подробно описаны в выпуске сертификатов Let's Encrypt с помощью certbot и nginx в Ubuntu 24.04:

sudo apt install nginx certbot python3-certbot-nginx
sudo certbot --nginx -d cloud.example.com

Certbot добавляет строки ssl_certificate и редирект :80:443, а также устанавливает таймер systemd, который обновляет 90-дневный сертификат. Проверьте его наличие с помощью systemctl list-timers | grep certbot; таймер обновления, который не был включен, — это 90-дневная «бомба замедленного действия».

Сам блок проксирования:

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;
    }
}

В nginx версии 1.25 и новее добавьте http2 on;. В Ubuntu 24.04 поставляется более старая сборка, где эквивалентом является listen 443 ssl http2;. Команда nginx -t покажет, какой из вариантов принимает ваша сборка.

client_max_body_size и длительные таймауты чтения предотвращают обрыв загрузки больших файлов на середине. proxy_request_buffering off передает поток загрузки напрямую, вместо того чтобы сначала записывать весь файл на диск прокси-сервера.

nginx на хосте — это самое простое решение для одного приложения. Если Nextcloud будет делить VPS с другими контейнерами, запуск Traefik в качестве обратного прокси через Docker Compose для нескольких приложений перенесет маршрутизацию и выпуск сертификатов в метки контейнеров, а те же параметры client_max_body_size и настройки таймаутов появятся там в виде middleware и параметров транспорта.

trusted_proxies и overwriteprotocol

Здесь допускается большинство ошибок при самостоятельной установке Nextcloud, а симптомы кажутся не связанными с первопричиной.

Параметр X-Forwarded-Proto: https учитывается только в том случае, если запрос поступает с адреса, указанного в trusted_proxies. Если он не учитывается, Nextcloud считает, что запрос пришел по обычному HTTP, и генерирует URL с http://; прокси перенаправляет их на HTTPS; браузер переходит по ссылке; Nextcloud снова генерирует http://. Это и есть цикл перенаправлений. Параметр OVERWRITEPROTOCOL: https принудительно задает схему независимо от условий.

Ловушка в TRUSTED_PROXIES заключается в том, что адрес, который «видит» Nextcloud, — это не 127.0.0.1. nginx работает на хосте и подключается к опубликованному порту, поэтому контейнер видит шлюз Docker bridge, обычно это адрес из диапазона 172.x. Узнайте реальную подсеть:

docker network inspect nextcloud_default \
  -f '{{range .IPAM.Config}}{{.Subnet}}{{end}}'

Добавьте этот CIDR (или покрывающий его 172.16.0.0/12) в TRUSTED_PROXIES. Если задать слишком широкий диапазон, любой клиент сможет подделать X-Forwarded-For; если задать неверный — все попытки входа будут выглядеть как запросы с адреса шлюза, защита от перебора паролей заблокирует весь экземпляр целиком, а в панели администратора появится сообщение: "Конфигурация заголовков обратного прокси-сервера неверна, или вы обращаетесь к Nextcloud через доверенный прокси-сервер."

Параметр OVERWRITECLIURL важен для контейнера cron, у которого нет входящего запроса, чтобы определить имя хоста. Без него фоновые задачи создают ссылки на localhost, а в уведомлениях по электронной почте отправляются нерабочие URL.

Фоновые задачи: cron вместо AJAX

По умолчанию в Nextcloud используется AJAX-метод запуска задач: они выполняются как побочный эффект при загрузке страницы пользователем. В 04:00 никто не работает с сайтом, поэтому очистка корзины, удаление старых версий файлов, генерация превью и повторные попытки федеративных запросов не выполняются. Первым признаком этой проблемы становится бесконечный рост размера каталога с данными. Сервис cron, описанный выше, запускает официальный цикл /cron.sh для тех же томов. Укажите Nextcloud, что нужно использовать этот метод:

docker compose exec -u www-data app php occ background:cron

Каждая команда occ имеет такой вид: docker compose exec -u www-data app php occ <command>. Рекомендуется создать для неё алиас.

Резервное копирование: три компонента или ничего

Копирование только файловой системы приводит к восстановлению неработоспособного экземпляра. Каталог данных содержит байты; Postgres хранит файловый кэш, общие ресурсы, пользователей и состояние приложения; config.php содержит учетные данные базы данных, ID экземпляра и соль пароля. Если восстановить файлы без базы данных, Nextcloud не сможет их распознать. Если восстановить базу данных без config.php, она не сможет открыться. Восстановление старой базы данных поверх более нового каталога данных приведет к тому, что общие ресурсы будут указывать на перемещенные файлы.

Выполняйте резервное копирование всех трех компонентов, предварительно переведя экземпляр в состояние покоя:

#!/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/

Режим обслуживания (maintenance mode) обеспечивает согласованность дампа и копии файлов. Если пропустить этот шаг, вы в конечном итоге получите дамп базы данных, ссылающийся на файл, до которого rsync еще не успел дойти. Обратите внимание, что скрипт сохраняет дампы базы данных с метками времени, но хранит только одно актуальное зеркало каталога данных: rsync --delete перезаписывает его при каждом запуске, поэтому только самый свежий дамп соответствует копии файлов.

Затем переместите данные за пределы сервера. Резервная копия, которая хранится на том же VPS, что и исходные данные, является лишь копией, а не резервной копией. Использование restic для передачи данных в объектное хранилище или на другой хост — стандартное решение, а дедупликация справляется с каталогом данных гораздо эффективнее, чем ежедневный tar-архив. Полная настройка, от инициализации репозитория до ежедневного таймера и тренировки восстановления, описана в резервном копировании VPS с использованием restic.

Восстановление — это не просто обратный процесс. Свежезапущенный стек выполняет установщик и записывает совершенно новый config.php, новый ID экземпляра и соль пароля. Импорт дампа поверх этой новой идентичности приведет к нерабочим сессиям и токенам общих ресурсов. Сначала восстановите старую идентичность в следующем порядке:

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 --all

files:scan приводит файловый кэш в соответствие с тем, что фактически находится на диске. Отрепетируйте этот процесс один раз на запасном VPS, прежде чем он вам понадобится. Такое же разделение между байтами на диске и метаданными в Postgres характерно для любого другого приложения подобного типа, поэтому резервная копия Immich, содержащая библиотеку, но не базу данных, восстанавливается в пустую временную шкалу.

Обновления: по одной мажорной версии за раз

Nextcloud поддерживает обновление строго на одну мажорную версию за один цикл. Переход с 29 сразу на 31 не завершается корректно: возникает ошибка Exception: Updates between multiple major versions and downgrades are unsupported., и система переходит в режим обслуживания (maintenance mode).

Процесс обновления в Docker выглядит так: создайте резервную копию, измените тег с 31 на 32 в сервисах app и cron, затем выполните docker compose pull && docker compose up -d, а после — docker compose logs -f app. Точка входа образа (entrypoint) обнаружит более новую версию кода при наличии существующих данных и самостоятельно запустит occ upgrade. Не прерывайте этот процесс. Когда в логах наступит тишина, выполните docker compose exec -u www-data app php occ status и проверьте versionstring, а также убедитесь, что приложения снова включены.

Два правила, которые помогут избежать проблем: обновляйте по одной мажорной версии, проверяйте работоспособность, затем переходите к следующей. Никогда не меняйте тег в сервисе app, не изменив при этом cron на соответствующий: использование двух разных версий Nextcloud с одной базой данных приведет к повреждению данных.

Ошибки, с которыми вы столкнетесь на практике

"Your data directory is readable by other users. Please change the permissions to 0770." (Ваш каталог данных доступен для чтения другим пользователям. Пожалуйста, измените права доступа на 0770). Каталог, подключенный через bind mount, имеет установленные биты чтения для группы или всех пользователей. sudo chmod 0770 /srv/nextcloud/data и sudo chown -R 33:33 /srv/nextcloud/data.

"Your data directory is invalid. Ensure there is a file called .ocdata in the root." (Ваш каталог данных недействителен. Убедитесь, что в корне находится файл .ocdata). Точка монтирования указывает на место, где Nextcloud не был инициализирован, допущена опечатка в пути или вместо рабочего экземпляра подставлен пустой каталог. Убедитесь, что путь на хосте совпадает с указанным в параметре volume.

"Access through untrusted domain." (Доступ через недоверенный домен). Имя хоста в запросе отсутствует в trusted_domains. NEXTCLOUD_TRUSTED_DOMAINS применяется только при первой установке; в дальнейшем изменяйте его «на лету»: occ config:system:set trusted_domains 1 --value=cloud.example.com.

502 Bad Gateway, с записью connect() failed (111: Connection refused) while connecting to upstream в /var/log/nginx/error.log. nginx не получил ответа по адресу 127.0.0.1:8080. Либо контейнер еще инициализируется (проверьте docker compose logs app), либо он завершил работу (docker compose ps), либо порт в параметре publish не совпадает с портом proxy_pass. Проверьте это с помощью ss -ltnp | grep 8080.

Циклическая переадресация или предупреждения о «небезопасном» соединении в панели администратора. Отсутствует OVERWRITEPROTOCOL: https или TRUSTED_PROXIES не содержит подсеть шлюза Docker. См. раздел о прокси выше.

LockedException: "files/..." is locked. При установленном REDIS_HOST образ настраивает Redis в качестве бэкенда для блокировок, и «зависшие» блокировки встречаются редко. Без этого блокировки хранятся в таблице базы данных oc_file_locks, и запрос, прерванный во время записи, оставляет «мусорные» строки. Прежде чем удалять строки блокировок вручную, убедитесь, что Redis действительно используется: occ config:system:get memcache.locking должен вернуть класс Redis.

"The PHP memory limit is below the recommended value of 512MB." (Лимит памяти PHP ниже рекомендуемого значения в 512 МБ). Увеличьте PHP_MEMORY_LIMIT и пересоздайте контейнер. Помните, как это влияет на максимально возможное потребление памяти в худшем случае.

Проблемы при масштабировании

Первый барьер — нехватка места в каталоге данных. Увеличение объема диска на VPS требует изменения размера раздела и файловой системы. Эту процедуру гораздо проще выполнить по расписанию, чем когда диск заполнен на 100%. Настройте оповещения об использовании диска сейчас, а не потом.

Второй барьер — oc_filecache. Скорость вывода списков файлов и сканирования для синхронизации падает при увеличении количества записей. Решение заключается в оптимизации базы данных: размещайте Postgres на быстром хранилище, выделите достаточно оперативной памяти для shared memory и настройте правила хранения для удаления старых версий и мусора, вместо того чтобы позволять им накапливаться бесконечно.

Третий барьер — генерация превью, которая конкурирует за ресурсы с остальными процессами. На маломощном сервере ограничьте количество провайдеров превью и никогда не запускайте occ preview:generate-all в рабочее время. Если основной объем данных составляют фотографии с телефона, перенесите генерацию миниатюр на специализированный фотосервер. В статье сравнение PhotoPrism и Immich по потреблению RAM, мобильным приложениям и командам резервного копирования описаны затраты на эти решения в сравнении с Nextcloud.

Помимо этого, честный ответ заключается в том, что дополнительные компоненты требуют отдельного сервера. Collabora и полнотекстовый поиск — это резидентные сервисы с собственными требованиями к памяти. Размещение их на том же сервере, где хранится единственная копия ваших файлов, увеличивает область отказа без какой-либо выгоды. Если вам нужно редактирование документов в браузере, минимальные требования к RAM и лимиты соединений для OnlyOffice и Collabora помогут определить, что именно потянет VPS с 2–4 GB оперативной памяти. Переносите хранилище файлов на S3-совместимые сервисы, когда текущий объем диска перестает соответствовать задачам. Учтите, что это усложняет резервное копирование: база данных по-прежнему содержит метаданные, и её дамп должен создаваться синхронно с содержимым бакета.

Когда инстанс начнет обслуживать реальных пользователей, установите Uptime Kuma, чтобы узнавать о простоях раньше, чем это сделают клиенты синхронизации. Частное облако хорошо сочетается с собственным почтовым сервером. Если вы не хотите настраивать сервисы вручную, сравнение Cloudron, CasaOS и Coolify поможет выбрать платформу, которая сделает это за вас. Если следующим шагом станет установка поисковой системы, будьте готовы к проблемам иного рода: ошибки 429 в SearXNG возникают либо из-за встроенного ограничителя частоты запросов, либо из-за блокировки IP-адреса вашего VPS вышестоящими поисковыми системами. Только логи помогут определить истинную причину.

FAQ

Можно ли запустить Nextcloud на SQLite вместо Postgres?

Можно, и официальный образ это позволяет, но один клиент синхронизации для рабочего стола при выполнении параллельных запросов приведет к SQLSTATE[HY000]: General error: 5 database is locked и ошибкам HTTP 500. SQLite устанавливает блокировку записи на всю базу данных, а Nextcloud постоянно записывает данные: блокировки файлов, строки активности, состояние заданий. Начинайте работу с Postgres или MariaDB; occ db:convert-type существует, но это длительная миграция «все или ничего» на работающих данных.

Сколько оперативной памяти на самом деле нужно VPS для Nextcloud?

Рассчитывайте объем исходя из параллельности запросов, а не количества пользователей. Максимальный объем резидентной памяти примерно равен количеству одновременных запросов, умноженному на PHP_MEMORY_LIMIT, плюс общие буферы Postgres и по одному бэкенду на каждое соединение, плюс пиковые значения при генерации превью. Сервер с 2 GB RAM потянет небольшой домашний экземпляр, если ограничить превью и добавить swap; при добавлении Collabora или полнотекстового поиска потребуется выделить ресурсы под второй набор резидентных сервисов.

Почему большие загрузки файлов прерываются при работе через reverse proxy nginx?

Обычно это связано с двумя настройками прокси: client_max_body_size, оставленный со значением по умолчанию 1 MB, обрезает запрос, а короткие значения proxy_read_timeout / proxy_send_timeout прерывают долгие передачи на середине. Установите оба значения с запасом, переключите proxy_request_buffering off в режим потоковой передачи (stream) вместо буферизации (spool) и увеличьте PHP_UPLOAD_LIMIT в контейнере приложения до соответствующих значений.

Почему Nextcloud уходит в цикл перенаправлений или выдает предупреждение о reverse proxy?

Контейнер не видит nginx на 127.0.0.1, он видит шлюз Docker bridge, находящийся в 172.x. Когда этот адрес отсутствует в TRUSTED_PROXIES, заголовок X-Forwarded-Proto: https игнорируется, Nextcloud генерирует URL с http://, и прокси возвращает их обратно. Установите TRUSTED_PROXIES в значение реальной подсети bridge и зафиксируйте OVERWRITEPROTOCOL: https.

Можно ли обновить Nextcloud с версии 29 сразу до 31?

Нет. Nextcloud поддерживает обновление только на одну мажорную версию за раз, а пропуск версий приведет к Updates between multiple major versions and downgrades are unsupported., оставив экземпляр в режиме обслуживания (maintenance mode). Сделайте резервную копию, увеличьте тег на одну мажорную версию для сервисов app и cron, выполните docker compose pull && docker compose up -d, проверьте результат с помощью occ status, затем повторите процедуру.