SSD Nodes Learn
Посібники Matt ConnorВід Matt Connor · Оновлено 2026-07-24

Встановлення Nextcloud на VPS через Docker

Налаштуйте Nextcloud за допомогою Docker Compose, Postgres та Redis. Покрокова інструкція з налаштування TLS через nginx та створення надійних backup-схем.

Що саме ви створюєте

Цей посібник передбачає запуск 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 record (та AAAA для IPv6), налаштований на cloud.example.com вашого VPS. Усе це потребує сервера під вашим повним контролем — неможливо виконати термінацію TLS та створення дампу бази даних у сторонніх SaaS-сервісах.

Sizing: що саме споживає пам'ять

Споживання пам'яті Nextcloud зумовлене трьома факторами, і жоден із них не є самим "Nextcloud".

PHP workers. Образ -apache обробляє кожен одночасний запит через робочий процес, що містить інтерпретатор PHP. Кожен worker може споживати до 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 kill під час оновлення системи — це набагато гірше.

Чому SQLite працює некоректно

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

Пізніше можна виконати конвертацію за допомогою occ db:convert-type, але це тригий міграційний процес «все або нічого» на живих даних. Починайте з Postgres або MariaDB.

Compose file

Помістіть цей файл у /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, а не named 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, таймер оновлення та сценарії помилок детально описані в issuing Let's Encrypt certificates with certbot and nginx on 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 та великі значення read timeouts запобігають перериванню завантажень великих файлів. proxy_request_buffering off пропускає завантаження через проксі потоком, замість того щоб спочатку зберігати весь файл на диск проксі.

nginx на хост-системі — найпростіший варіант для одного додатка. Якщо Nextcloud буде працювати на тому ж VPS, що й інші контейнери, running Traefik as a Docker Compose reverse proxy for multiple apps дозволяє перенести маршрутизацію та видачу сертифікатів у labels контейнерів. У такому разі питання client_max_body_size та тайм-аутів вирішуються через налаштування middleware та transport.

trusted_proxies та overwriteprotocol

Це основна причина помилок у більшості self-hosted інстансів Nextcloud, хоча симптоми не здаються пов'язаними з причиною.

X-Forwarded-Proto: https застосовується лише тоді, коли запит надходить з адреси, вказаної в trusted_proxies. Якщо цей параметр не застосовується, Nextcloud вважає запит звичайним HTTP і генерує http:// URL; проксі перенаправляє їх на HTTPS; браузер виконує перенаправлення; Nextcloud знову генерує http://. Це створює цикл перенаправлень (redirect loop). 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; якщо налаштувати неправильно, кожен вхід у систему виглядатиме як запит від адреси шлюзу. Це призведе до того, що захист від brute-force заблокує весь інстанс, а в огляді адміністратора з'явиться повідомлення: "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 ніхто не переглядає сторінки, очищення смітника, видалення версій, створення прев'ю та повторні спроби federated-запитів зупиняються. Першою ознакою є постійне зростання об'єму даних у директорії даних. Вищезгадана служба 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.

Backups: три елементи або нічого

Резервне копіювання лише файлової системи не дозволить відновити пошкоджену інстанцію. Директорія з даними містить байтовий вміст; Postgres зберігає кеш файлів, посилання, користувачів та стан додатка; config.php зберігає облікові дані бази даних, ID інстанції та сіль пароля. Якщо відновити файли без бази даних, Nextcloud не зможе їх побачити. Якщо відновити базу даних без config.php, вона не зможе відкрити базу. Якщо відновити стару базу даних на нову директорію з даними, посилання будуть вказувати на файли, які змінили розташування.

Зробіть резервну копію всіх трьох елементів з інстанції у стані quiesced:

#!/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 з об'єктним сховищем або другим хостом; функція дедуплікації в ньому працює з директорією даних набагато ефективніше, ніж щоденні tarball. Повний процес, від ініціалізації репозиторію до щоденного таймера та перевірки відновлення, описано в off-box VPS backups with 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 синхронізує кеш файлів із фактичним вмістом на диску. Відпрацюйте цей процес один раз на віртуальному сервері, перш ніж він знадобиться вам у реальних умовах.

Оновлення: по одній основній версії за раз

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) або рядок publish не відповідає порту proxy_pass. Перевірте за допомогою ss -ltnp | grep 8080.

Цикл перенаправлень (redirect loop) або попередження "insecure" в огляді адміністратора. Відсутній OVERWRITEPROTOCOL: https або TRUSTED_PROXIES не містить підмережі Docker gateway. Див. розділ про проксі вище.

LockedException: "files/..." is locked. Якщо встановлено REDIS_HOST, образ налаштовує Redis як бекенд для блокувань (locking), що мінімізує кількість застарілих блокувань. Без цього блокування зберігаються в таблиці бази даних 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 та один бекенд на кожне з'єднання, плюс пікові навантаження на генерацію прев'ю. Сервер з 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. і переведе інстанцію в режим обслуговування (maintenance mode). Зробіть резервну копію, оновіть теги на одну мажорну версію для служб app та cron, виконайте docker compose pull && docker compose up -d, перевірте через occ status, а потім повторіть процедуру.