MAATRIX / Блог / Trilium Notes на сервере: частые ошибки и решения

Trilium Notes на сервере: частые ошибки и решения

MAATRIX

Trilium Notes — одна из немногих self-hosted систем заметок, которая реально держит дерево из тысяч записей без тормозов и не просит подписку. Но именно на сервере, а не на локальном компьютере, всплывают проблемы, которых нет в документации: WebSocket рвётся за обратным прокси, SQLite блокируется при неаккуратном монтировании тома, а синхронизация между desktop-клиентом и сервером падает из-за банального рассогласования версий. Ниже — конкретные причины и рабочие исправления, без «попробуйте переустановить».

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →

Установка: Docker или бинарник, и на чём спотыкаются новички

Официальный путь — контейнер triliumnext/notes (сообщество продолжило разработку после того, как оригинальный автор притормозил проект; на бинарники и старые образы zadam/trilium полагаться не стоит — они не обновляются). Рабочий docker-compose.yml:

services:
  trilium:
    image: triliumnext/notes:latest
    container_name: trilium
    restart: unless-stopped
    ports:
      - "8080:8080"
    volumes:
      - ./trilium-data:/home/node/trilium-data
    environment:
      - TRILIUM_DATA_DIR=/home/node/trilium-data

Три ошибки, которые встречаются постоянно:

  • Порт 8080 уже занят. На VPS часто крутится ещё что-то на 8080 (панель управления, другой сервис). Проверьте ss -tulpn | grep 8080 перед запуском и при конфликте меняйте маппинг на "8081:8080".
  • Данные "пропадают" после пересоздания контейнера. Если volume смонтирован не тем путём (или используется анонимный том без явного volumes:), docker compose down вместе с docker compose up на новом хосте создаст пустую базу. Всегда держите trilium-data на явном bind-mount и делайте docker compose config перед первым запуском в проде, чтобы увидеть, куда реально смотрит том.
  • Permission denied при записи в /home/node/trilium-data. Контейнер работает от пользователя node (обычно uid 1000). Если каталог на хосте создан от root или другого uid, Trilium не сможет писать в базу и просто зависнет на старте. Решение:
mkdir -p ./trilium-data
sudo chown -R 1000:1000 ./trilium-data

Общая логика разворачивания контейнерных сервисов на голом VPS — с volumes, сетями и restart-политиками — подробно разобрана в статье про частые ошибки Docker Compose в проде: большинство проблем с Trilium — это те же самые типовые грабли Docker, просто на конкретном сервисе.

Nginx и WebSocket: почему рвётся синхронизация через прокси

Это причина №1 обращений в поддержку по Trilium на сервере. Приложение использует WebSocket не только для live-обновления интерфейса, но и для синхронизации между вкладками и клиентами. Если прокси не пробрасывает Upgrade-заголовки, интерфейс грузится, но начинает "залипать": изменения не появляются без ручного обновления страницы, а в консоли браузера — постоянные обрывы соединения.

Рабочий блок для Nginx:

server {
    listen 443 ssl http2;
    server_name notes.example.com;

    ssl_certificate     /etc/letsencrypt/live/notes.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/notes.example.com/privkey.pem;

    client_max_body_size 100M;

    location / {
        proxy_pass http://127.0.0.1:8080;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        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_read_timeout 3600s;
    }
}

Ключевые моменты, которые обычно упускают:

  • proxy_http_version 1.1 обязателен — без него апгрейд до WebSocket невозможен в принципе.
  • proxy_read_timeout по умолчанию в Nginx — 60 секунд. Trilium держит WS-соединение долго, и если вы не увеличите таймаут, каждые 60 секунд соединение будет тихо рваться и переустанавливаться — заметки при этом не теряются, но интерфейс подёргивается.
  • client_max_body_size нужен, если вы прикрепляете к заметкам файлы или картинки крупнее 1 МБ (дефолт Nginx) — иначе загрузка вложений будет падать с 413 Request Entity Too Large без внятного сообщения в самом Trilium.

Если вместо Nginx используете Caddy или Traefik, суть та же: явно прописывать поддержку WebSocket отдельно почти никогда не нужно (они это делают по умолчанию), но проверить таймауты всё равно стоит.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

SSL/HTTPS и config.ini: доступ через обратный прокси

Trilium из коробки не «знает», что стоит за HTTPS-прокси, и по умолчанию может некорректно работать с secure-куками или считать, что соединение небезопасно. В файле config.ini (лежит в корне data-каталога, то есть в вашем trilium-data/config.ini) есть секция [Network]:

[Network]
host=0.0.0.0
port=8080
https=false
trustedReverseProxy=true

trustedReverseProxy=true говорит приложению доверять заголовкам X-Forwarded-* от прокси и не пытаться самому терминировать TLS — это правильная конфигурация, когда HTTPS обрабатывает Nginx или Caddy снаружи контейнера. После правки файла контейнер нужно перезапустить:

docker compose restart trilium

Отдельная головная боль — сертификат, который переставал обновляться сам, оставляя браузер с предупреждением о просроченном TLS перед формой логина Trilium. Если сертификат не продлевается через certbot/acme.sh автоматически, разберите причины и чек-лист в статье «SSL-сертификат не обновился» — там разобраны типичные блокировки: занятый 80-й порт, неверный webroot, просроченный cron-таймер.

SQLite: "database is locked" и повреждение базы

Trilium хранит все заметки, версии и вложения в одном файле document.db (SQLite, через better-sqlite3). Это быстро и просто, но у SQLite один писатель одновременно — и здесь кроется большинство «загадочных» падений на сервере.

Ошибка SQLITE_BUSY: database is locked почти всегда означает, что к одному и тому же document.db пытаются достучаться два процесса Trilium одновременно. Частые сценарии:

  • Вы одновременно держите desktop-приложение, указывающее на тот же смонтированный каталог (например, через сетевую шару), и серверный контейнер.
  • При деплое старый контейнер не успел корректно остановиться (docker compose up -d поверх ещё живого процесса) — два инстанса пишут в один volume.
  • Файл document.db лежит на сетевой файловой системе (NFS, SMB-mount, некоторые сетевые диски у облачных провайдеров), которая не даёт SQLite нормально работать с блокировками файлов.

Решение — держать ровно один активный процесс на данные и данные только на локальном диске (или блочном сетевом томе, который монтируется как обычный диск, а не как NFS-шара). Перед перезапуском убедитесь, что старый контейнер действительно остановлен:

docker compose down
docker ps -a | grep trilium
docker compose up -d

Повреждение базы после аварийного выключения (kill -9, обрыв питания, OOM-killer прибил процесс во время записи) проявляется как database disk image is malformed при старте. Первым делом — проверка целостности:

docker compose stop trilium
sqlite3 ./trilium-data/document.db "PRAGMA integrity_check;"

Если проверка находит ошибки, восстановление обычно делается через дамп и пересборку базы (sqlite3 document.db ".dump" > dump.sql, затем создание нового файла из дампа) — но перед экспериментами всегда сначала копируйте повреждённый файл в сторону, а не правьте оригинал. Именно поэтому регулярный бэкап здесь не факультативная опция, а обязательная часть эксплуатации — см. раздел ниже.

Синхронизация между desktop-клиентом и сервером: конфликты версий

Trilium умеет работать как «сервер синхронизации»: desktop-приложение на ноутбуке синхронизируется с вашим VPS, а сервер, в свою очередь, доступен и через браузер. Ошибка Sync check failed, requesting full sync или прямое сообщение о несовпадении протокола версии почти всегда значит одно — desktop-клиент и серверный образ разошлись по major-версии.

Правило простое: обновляйте сервер и все desktop-клиенты синхронно, не оставляйте клиент на старой версии месяцами, пока сервер уезжает вперёд по релизам. Проверить версию сервера можно через API:

curl -s http://127.0.0.1:8080/api/app-info | grep -i version

Если после обновления одной стороны синхронизация зависла в цикле «full sync» и не может завершиться — обычно помогает временно уменьшить объём: экспортировать проблемную ветку заметок в .zip через интерфейс, проверить, что базовая синхронизация пустого дерева проходит, а затем импортировать данные обратно. Полный full-sync большого дерева (десятки тысяч заметок с ревизиями) может занимать заметное время и упирается в производительность диска на сервере — на HDD-тарифах это будет ощутимо дольше, чем на NVMe.

Ещё один источник конфликтов — два desktop-клиента, синхронизирующиеся с одним сервером и одновременно правящие одну и ту же заметку оффлайн. Trilium создаёт конфликтную копию заметки, а не молча теряет правки — но разбирать такие копии руками неприятно, поэтому если несколько человек работают с одной базой, лучше держать сервер как единственный источник правды и не редактировать в оффлайн-режиме подолгу.

Бэкап и восстановление данных

У Trilium есть встроенный механизм бэкапа — при активной опции он копирует document.db по расписанию в подкаталог backup/ внутри data-директории. Настраивается в config.ini:

[Backup]
enableDailyBackup=true
enableWeeklyBackup=true
enableMonthlyBackup=true

Это удобно для быстрого отката на день-два назад, но не заменяет полноценный внешний бэкап: если сгорит диск сервера целиком, локальные копии в том же volume уйдут вместе с оригиналом. Поскольку вложения и картинки хранятся прямо внутри document.db как blob-поля, backup всего этого файла — это backup буквально всех данных заметок, отдельно копировать ничего не нужно.

Минимальная схема для VPS — снимать весь каталог trilium-data наружу по расписанию:

# перед снятием копии стоит остановить запись, чтобы не поймать базу в момент транзакции
docker compose stop trilium
tar -czf /backup/trilium-$(date +%F).tar.gz ./trilium-data
docker compose start trilium

Для регулярного и версионированного бэкапа с шифрованием и дедупликацией разумнее взять готовый инструмент вместо cron с tar — как это настроить для Docker-томов, подробно разобрано в статье про бэкап Docker volume на сервере. Восстановление — обратная операция: остановить контейнер, распаковать архив на место trilium-data, поправить владельца (chown -R 1000:1000) и запустить снова.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Нужны сами нейросети для контента?

Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.

Частые вопросы

Сколько ресурсов нужно Trilium на сервере?

Для личного использования с несколькими тысячами заметок обычно достаточно 1 vCPU и 1–2 ГБ RAM — сама Node.js-часть держит скромный footprint, основная нагрузка на диск при полной синхронизации и открытии больших заметок. Ориентируйтесь по факту через docker stats, точные цифры зависят от объёма вложений и глубины истории версий.

Можно ли открыть Trilium без пароля, только для себя за VPN?

Да, через noAuthentication=true в [Network] секции config.ini, но это имеет смысл только если сервер вообще не смотрит в открытый интернет напрямую — например, доступен только через WireGuard-туннель или закрыт файрволом по IP.

Что делать, если после обновления образа Trilium не стартует?

Проверьте логи (docker compose logs -f trilium) на предмет ошибки миграции схемы базы — перед крупным обновлением (переход через несколько major-версий сразу) стоит сначала снять бэкап document.db, а уже потом обновлять образ, поскольку откат миграции назад штатно не поддерживается.

Trilium подходит как замена корпоративной wiki для команды?

Он не рассчитан на одновременное редактирование одной заметки несколькими людьми (в отличие от классических wiki-движков с версионированием под совместную работу) — для командной базы знаний с разграничением прав и совместным редактированием ближе окажется решение вроде Wiki.js или Outline, а Trilium силён именно как личная иерархическая система заметок с мощным поиском.

Нужен ли отдельный домен и SSL, если Trilium используется только локально?

Если доступ идёт исключительно через VPN или SSH-туннель (ssh -L 8080:localhost:8080 user@server), можно обойтись без публичного домена и сертификата вообще — trustedReverseProxy и Nginx в этом случае не нужны.

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →