SSD Nodes Learn
Руководства Matt ConnorАвтор: Matt Connor · Обновлено 2026-07-24

Как установить Nextcloud на VPS через Docker

Пошаговое руководство по развертыванию Nextcloud с использованием Docker Compose, Postgres, Redis и TLS. Узнайте, как правильно настроить бэкапы и обновления.

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

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

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

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

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

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

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

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

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

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

Почему 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:

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

Директория данных намеренно настроена как bind mount, а не как именованный volume: прямой доступ инструмента резервного копирования к пути важнее чистоты структуры. Создайте директорию с 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. При первом запуске все дерево приложений копируется в volume и запускается установщик; контейнер не будет отвечать на запросы до завершения этого процесса.

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

Установите nginx и certbot из репозиториев дистрибутива. Создайте обычный server block для порта 80 с правильным server_name, затем выполните перенастройку через certbot. Механика HTTP-01 challenge, таймер обновления и причины сбоев подробно описаны в выпуск сертификатов 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 timer для обновления 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 для нескольких приложений переносит маршрутизацию и выпуск сертификатов в labels контейнеров. В этом случае вопросы client_max_body_size и таймаутов решаются через настройки middleware и transport.

trusted_proxies и overwriteprotocol

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

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

Проблема в 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. Если указать неверно, все входы в систему будут отображаться как исходящие с адреса шлюза. В этом случае защита от перебора паролей (brute-force protection) заблокирует весь инстанс сразу, а в обзоре администратора появится сообщение: "The reverse proxy header configuration is incorrect, or you are accessing Nextcloud from a trusted proxy."

Параметр 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>. Рекомендуется создать для нее alias.

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

Резервное копирование только файловой системы не восстановит поврежденный экземпляр. В директории данных хранятся байты; 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, прежде чем он потребуется вам в реальных условиях.

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

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." Директория, примонтированная через 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." Bind mount указывает на директорию, которую 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), либо строка публикации порта не совпадает с портом proxy_pass. Проверьте с помощью ss -ltnp | grep 8080.

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

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

"The PHP memory limit is below the recommended value of 512MB." Увеличьте PHP_MEMORY_LIMIT и пересоздайте контейнер. Учитывайте, как это повлияет на максимально допустимый предел памяти.

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

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

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

Третье ограничение — конкуренция за ресурсы при генерации превью. На слабых серверах ограничьте список поставщиков превью и не запускайте occ preview:generate-all в рабочее время.

Далее: дополнительные сервисы требуют выделенных ресурсов. Collabora и полнотекстовый поиск — это отдельные резидентные службы со своими профилями потребления памяти. Размещение этих служб на том же сервере, где хранятся ваши файлы, увеличивает область отказа без какой-либо выгоды. Перенесите хранение файлов в S3-совместимое хранилище, когда текущий том станет недостаточно эффективным. Учтите, что это усложняет резервное копирование: метаданные по-прежнему хранятся в базе данных, и дамп базы должен выполняться синхронно с бэкапом бакета.

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

FAQ

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

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

Сколько RAM на самом деле требуется для VPS с Nextcloud?

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

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

Причина обычно в двух настройках прокси: значение 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 на реальную подсеть моста и зафиксируйте OVERWRITEPROTOCOL: https.

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

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