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

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

MAATRIX

Standard Notes выбирают не за красивый интерфейс, а за две вещи: заметки шифруются на устройстве до отправки на сервер, и формат данных открыт — экспорт читается даже без самого приложения. Но именно это делает self-hosted вариант капризным: сервер не видит содержимого, поэтому любая ошибка конфигурации молча ломает синхронизацию, а не выдаёт понятную подсказку. Ниже — конкретные симптомы, с которыми сталкиваются при поднятии Standard Notes на своём VPS, и как их закрывать по порядку.

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

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

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

Как устроен self-hosted Standard Notes и что может пойти не так

Официальный self-hosted стек Standard Notes (репозиторий standardnotes/server) — это не один процесс, а связка сервисов, поднимаемых через Docker Compose:

  • server — основной контейнер, который под капотом через pm2 запускает несколько внутренних служб: API-шлюз, сервис синхронизации, авторизацию;
  • db — реляционная база (в актуальных версиях по умолчанию MySQL, часть форков позволяет PostgreSQL);
  • cache — Redis, обязателен для очередей и сессий, без него сервер не стартует;
  • опционально — files server, отдельный сервис для хранения вложений (без него текстовые заметки синхронизируются, но загрузка файлов не работает).

Плюс к этому нужен обратный прокси с HTTPS — сам сервер по умолчанию слушает HTTP на внутреннем порту и не занимается сертификатами.

Из этой схемы вытекает почти вся диагностика: если что-то не работает, вопрос всегда один и тот же — какое именно звено цепочки «клиент → прокси → server → db/redis» отвалилось. Дальше разбираем по звеньям.

Контейнер server падает сразу после запуска

Самая частая ситуация при первом деплое: docker compose up -d отрабатывает, но через несколько секунд контейнер server уходит в restart loop.

Первым делом смотрим логи:

docker compose logs -f server --tail=100

В подавляющем большинстве случаев причина — секретные переменные окружения в .env. Standard Notes требует набор криптографических ключей и секретов (в разных версиях они называются примерно так: AUTH_JWT_SECRET, ENCRYPTION_SERVER_KEY, PSEUDO_KEY_PARAMS_KEY, VALET_TOKEN_SECRET — точный список и имена смотрите в .env.sample вашей версии репозитория, они менялись между релизами). Если оставить их пустыми, скопировать одинаковое значение во все переменные или сгенерировать слишком короткую строку — сервис падает на старте с ошибкой валидации конфигурации, часто без явного указания, какая именно переменная виновата.

Решение — сгенерировать каждому секрету своё уникальное 32-байтное значение:

openssl rand -hex 32

Прогоните команду отдельно для каждой переменной, не копируйте один и тот же результат в несколько полей — некоторые из них участвуют в разных частях криптографии протокола и обязаны различаться.

Вторая по частоте причина падения — контейнер server стартует раньше, чем база данных готова принимать соединения. depends_on в Docker Compose без condition: service_healthy гарантирует только порядок запуска процессов, а не готовность MySQL внутри. Добавьте healthcheck на сервис db:

db:
  image: mysql:8
  healthcheck:
    test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
    interval: 5s
    timeout: 5s
    retries: 10
server:
  depends_on:
    db:
      condition: service_healthy

Это убирает race condition при холодном старте — особенно заметно на первом запуске, когда MySQL инициализирует data directory дольше обычного.

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

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

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

Проблемы с базой: кодировка, миграции, connection refused

Если контейнер поднялся, но заметки не сохраняются или сервер падает уже после нескольких минут работы, смотрите в сторону базы.

Кодировка. Зашифрованный контент заметки — это base64-подобная строка, которая по факту хранится как обычный текст, но может быть довольно длинной и содержать символы, которые не помещаются в utf8 (не utf8mb4) при определённых длинах. Если база или конкретная таблица создана с charset utf8 вместо utf8mb4, часть операций записи будет обрываться по ошибке усечения данных. Проверьте:

SHOW CREATE DATABASE standardnotes;
SHOW TABLE STATUS FROM standardnotes;

Если видите utf8 без mb4 — пересоздайте базу с правильной кодировкой до того, как в ней появятся боевые данные:

CREATE DATABASE standardnotes CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

Connection refused / Access denied. Классическая рассинхронизация переменных: в .env контейнера server указан один пароль, а в переменных инициализации MySQL — другой, либо хост базы прописан как localhost вместо имени сервиса в docker-сети (db). Контейнеры общаются друг с другом по именам сервисов, а не через localhost — это частая ошибка у тех, кто переносит конфиг с локальной машины. Общие подходы к диагностике MySQL на сервере (проверка прав, сокетов, bind-address) разобраны в статье MySQL на сервере: частые ошибки и решения — логика применима и здесь, только «клиентом» выступает не консольный mysql, а сам контейнер Standard Notes.

Миграции. При обновлении версии сервера структура таблиц может меняться. Если после docker compose pull && docker compose up -d сервис не стартует именно на новой версии образа (а на старой работал), проверьте в логах упоминания миграций — иногда их нужно прогнать вручную командой, специфичной для вашей версии (обычно указана в CHANGELOG репозитория). Пропуск этого шага — самая частая причина «вчера всё работало, обновился — всё сломалось».

Redis и ошибка «Invalid or missing api endpoint» в клиенте

Redis в этом стеке — не опция для ускорения, а обязательный компонент: через него идут очереди задач синхронизации и хранение части сессионных данных. Если Redis недоступен, сервер либо не стартует, либо стартует, но синхронизация зависает без явной ошибки в интерфейсе — заметки просто перестают появляться на других устройствах.

Проверка изнутри сети контейнеров:

docker compose exec server sh -c 'nc -zv cache 6379'

Если соединение не проходит — смотрите REDIS_URL (или отдельные REDIS_HOST/REDIS_PORT в зависимости от версии) в .env: типичная ошибка — указать 127.0.0.1 вместо имени сервиса cache, либо забыть, что образ Redis по умолчанию не слушает вообще ничего, если ему не передан флаг конфигурации. Общие сценарии отказа Redis — таймауты, misconf, потеря данных после рестарта — разобраны отдельно в статье Redis: отказ в подключении — причины и решение, стоит свериться, если проблема не в самой связке с Standard Notes, а в Redis как таковом.

Отдельная категория проблем — на стороне клиента (веб, desktop или мобильное приложение). Standard Notes по умолчанию ходит на официальный облачный сервер, и чтобы использовать свой — нужно на экране входа явно включить продвинутые настройки и указать URL вашего API-шлюза (обычно это https://ваш-домен с путём, зависящим от версии клиента). Ошибка «Invalid or missing api endpoint» почти всегда означает одно из двух: либо URL указан с опечаткой/без https, либо сервер отвечает не на том пути, который ожидает клиент — например, вы направили клиента на порт самого контейнера напрямую, минуя reverse proxy, который и должен разруливать маршрутизацию запросов между внутренними сервисами.

HTTPS через reverse proxy — без него работать не будет

Контейнер server не занимается TLS-сертификатами и слушает обычный HTTP на внутреннем порту (в docker-сети, не наружу). Все клиенты Standard Notes — веб, desktop, мобильные — требуют HTTPS для подключения к кастомному серверу; исключений на практике нет, даже для локального тестирования протокол настойчиво просит защищённое соединение.

Рабочая связка — Caddy как reverse proxy перед сервером, он же берёт на себя автоматический выпуск и продление сертификатов Let's Encrypt:

notes.example.com {
    reverse_proxy server:3000
}

Порт нужно свериться с тем, что реально слушает ваш контейнер server в текущей версии образа — он не всегда 3000, иногда шлюз и внутренние сервисы разнесены по разным портам и Caddy нужно направлять именно на API-шлюз, а не на внутренний воркер синхронизации.

Если в процессе настройки Caddy сертификат не выпускается или процесс зависает на «obtaining certificate» — почти всегда виноват DNS (A-запись домена ещё не указывает на IP сервера) либо закрытый порт 80/443 в файрволе. Пошаговый разбор типичных причин и решений — в статье Caddy с авто-SSL на сервере: частые ошибки и решения. Там же нюанс, который часто упускают: Let's Encrypt делает запрос по HTTP-01 challenge именно на 80 порт, и если он закрыт правилами UFW — сертификат не выпустится, даже если 443 открыт. Базовые правила файрвола для такого сценария описаны в UFW на сервере: частые ошибки и решения.

Бэкапы и перенос на другой сервер

Здесь у Standard Notes есть приятная особенность: раз контент шифруется на стороне клиента, дамп базы сам по себе не раскрывает содержимого заметок при утечке бэкапа — это часть заявленной модели приватности. Но это не отменяет необходимости бэкапить: потеря базы означает потерю заметок навсегда, шифрование не спасает от отказа диска.

Минимальный набор для бэкапа — это дамп базы данных и volume с загруженными файлами (если используете файловый сервер):

docker compose exec db mysqldump -u root -p standardnotes > standardnotes_$(date +%F).sql
docker run --rm -v standardnotes_files:/data -v $(pwd):/backup alpine \
  tar czf /backup/files_$(date +%F).tar.gz -C /data .

Дамп стоит снимать консистентно (без активной записи в момент снятия, либо с --single-transaction для InnoDB), иначе на восстановлении можно получить повреждённые строки. Общие подходы к автоматизации и хранению бэкапов MySQL — в статье бэкап MySQL на сервере: частые ошибки и решения, а если бэкапите весь стек через volume целиком, а не только базу — пригодится бэкап Docker volume на сервере: частые ошибки и решения.

При переносе на другой сервер главное — не забыть перенести и .env с теми же секретами (AUTH_JWT_SECRET, ENCRYPTION_SERVER_KEY и остальные): если на новом сервере сгенерировать секреты заново, старые пользовательские сессии и часть привязанной к серверу криптографии перестанут быть валидными, и пользователям придётся перелогиниваться заново. Домен тоже переносите аккуратно — если меняется IP, обновите A-запись заранее, чтобы не ловить downtime синхронизации на время распространения DNS.

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

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

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

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

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

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

Можно ли использовать PostgreSQL вместо MySQL?

В части версий и форков — да, но официальный образ по умолчанию ориентирован на MySQL. Перед переходом на PostgreSQL проверьте, поддерживает ли конкретная версия server, которую вы разворачиваете, эту базу — иначе миграции могут не пройти.

Почему заметки не появляются на втором устройстве, хотя ошибок нет?

В 9 случаях из 10 — Redis недоступен или очередь синхронизации зависла. Проверьте логи контейнера server на упоминания подключения к Redis и перезапустите связку server + cache вместе.

Нужен ли отдельный сервер для вложений (файлов)?

Только если вы используете загрузку файлов в заметки. Текстовые заметки и обычный markdown синхронизируются без файлового сервера — это отдельный опциональный компонент.

Что будет с заметками, если сервер полностью упадёт и бэкапа нет?

Заметки будут потеряны безвозвратно — шифрование защищает содержимое от чтения третьими лицами, но никак не от физической потери данных. Бэкап базы обязателен, это не опция «для параноиков».

Сколько ресурсов нужно серверу под small self-hosted инстанс?

Для личного использования или небольшой команды хватает 1-2 vCPU и 2 ГБ RAM — основная нагрузка на памяти дают MySQL и Redis, сам сервис Standard Notes лёгкий. При росте числа пользователей узкое место обычно упирается в базу, а не в сам сервер.

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

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

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