Бэкап и восстановление CryptPad
CryptPad — редактор документов, таблиц и досок с end-to-end шифрованием: сервер хранит только зашифрованные блобы и понятия не имеет, что в них лежит. Это удобно для приватности, но переворачивает привычную логику бэкапа: нет базы, которую можно продампить и посмотреть, всё ли на месте, а если потерять не тот кусок файловой структуры — данные не «повредятся», они станут нечитаемым мусором навсегда, без шанса на ручное восстановление. Ниже — рабочая схема бэкапа и восстановления self-hosted CryptPad на VPS, без магии и с честными ограничениями подхода.
Содержание
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →Что хранит CryptPad и почему это не «одна папка»
CryptPad — Node.js-приложение, которое почти всегда разворачивают в Docker (официальный образ cryptpad/cryptpad) или из исходников через git clone + npm run install:all. В обоих случаях состояние сервера живёт не в базе данных, а в наборе директорий на диске, каждая из которых отвечает за свою часть:
blob/— крупные загруженные файлы (вложения, картинки, PDF), хранятся в зашифрованном виде кусками, разложенными по подпапкам-префиксам.block/— служебные блоки для входа зарегистрированных пользователей: они позволяют серверу подтвердить, что человек знает пароль, не раскрывая сервером сам пароль или производный ключ.data/— метаданные: pin-store (какие документы «закреплены» за каким аккаунтом для подсчёта квоты), очереди задач, логи, список банов.datastore/— сами документы. Технически это не файлы в привычном смысле, а append-only журналы правок на канал (пад), зашифрованные целиком end-to-end — сервер физически не может прочитать их содержимое, только хранить и отдавать байты.config/config.js— главный конфиг: домен, админ-ключи (adminKeys), пути ко всем перечисленным выше директориям (blobPath,blockPath,filePathи т.д.), настройки квот и SMTP.customize/— если вы меняли брендинг, логотип, тексты на странице логина — они тоже здесь и не восстановятся сами.
Важное следствие шифрования на клиенте: сервер не хранит ключей расшифровки — они существуют только в URL пада (после #) и в браузере/памяти пользователя. Это значит, что бэкап сервера защищает от потери *сервера*, но не защищает от потери пользователем своей ссылки на пад — этот риск в принципе не закрывается бэкапом на вашей стороне, и об этом стоит прямо предупреждать команду, если вы администрируете CryptPad для коллег.
Ручной бэкап: тома, конфиг, ключ администратора
Первый бэкап лучше сделать руками, чтобы увидеть реальные размеры и понять, сколько займёт архивация. Если CryptPad запущен в Docker с типовым docker-compose (образ монтирует внутренние пути /cryptpad/blob, /cryptpad/block, /cryptpad/data, /cryptpad/datastore, /cryptpad/customize, /cryptpad/config на bind-mount с хоста), архивируем именно хостовые директории:
# Останавливаем контейнер, чтобы append-only журналы в datastore
# не дописывались во время архивации
docker compose -f /app/cryptpad/docker-compose.yml stop cryptpad
mkdir -p /backup/cryptpad
tar -czf /backup/cryptpad/cryptpad_$(date +%F).tar.gz \
-C /app/cryptpad \
blob block data datastore customize config
docker compose -f /app/cryptpad/docker-compose.yml start cryptpad
Если CryptPad развёрнут не в Docker, а из исходников как systemd-сервис, пути будут относительными от корня установки (обычно /opt/cryptpad/... или домашняя директория пользователя, от которого запущен сервис) — посмотрите значения blobPath, blockPath, filePath, pinPath (или data-путь) в вашем config/config.js, чтобы не архивировать «не туда».
Остановка на время архивации — не строгое требование (журналы в datastore пишутся дозаписью, а не перезаписью, так что грубая порча маловероятна), но рекомендуется: во время tar активная запись может увеличить окно, в котором вы поймаете документ в промежуточном состоянии между двумя правками. Для сервера с постоянной активной работой команды простой в несколько секунд ночью незаметен; если простой недопустим — используйте rsync с флагом --checksum вместо tar на живых данных, это снижает, но не убирает риск гонки.
Отдельно, вручную и в защищённом месте, сохраните:
grep -A5 "adminKeys" /app/cryptpad/config/config.js > /backup/cryptpad/admin-keys_$(date +%F).txt
Это список публичных ключей администраторов панели — если конфиг потеряется без этого файла, вы восстановите документы пользователей, но потеряете доступ к админ-панели самого CryptPad и не сможете забанить, разбанить или посмотреть статистику инстанса без повторной ручной настройки ключей.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверАвтоматизация: скрипт и cron
Ручной прогон — разовая проверка, дальше нужен скрипт с ротацией:
#!/bin/bash
# /usr/local/bin/cryptpad-backup.sh
set -euo pipefail
SRC=/app/cryptpad
DEST=/backup/cryptpad
DATE=$(date +%F)
RETENTION_DAYS=14
COMPOSE_FILE="$SRC/docker-compose.yml"
mkdir -p "$DEST"
docker compose -f "$COMPOSE_FILE" stop cryptpad
tar -czf "$DEST/cryptpad_$DATE.tar.gz" \
-C "$SRC" blob block data datastore customize config
docker compose -f "$COMPOSE_FILE" start cryptpad
# чистим архивы старше RETENTION_DAYS
find "$DEST" -name 'cryptpad_*.tar.gz' -mtime +"$RETENTION_DAYS" -delete
chmod +x /usr/local/bin/cryptpad-backup.sh
crontab -e
# бэкап CryptPad каждую ночь в 03:40
40 3 * * * /usr/local/bin/cryptpad-backup.sh >> /var/log/cryptpad-backup.log 2>&1
Нюанс, о который спотыкаются в проде: если инстанс используется распределённой командой в разных часовых поясах, «тихого» ночного окна может не быть вообще. В этом случае либо принимайте минимальный риск гонки при tar на живых данных, либо переключайтесь на снапшоты файловой системы (LVM, ZFS, снапшот диска у провайдера) — они консистентны без остановки сервиса.
Копии вне сервера: rclone и restic
Бэкап на том же диске, что и сам сервер, не переживёт отказ этого диска или компрометацию сервера целиком — его нужно синхронизировать наружу. Простой вариант — rclone на S3-совместимое хранилище:
rclone sync /backup/cryptpad remote-s3:cryptpad-backups \
--transfers 4 --checksum
Добавьте эту строку в конец cryptpad-backup.sh после find ... -delete, чтобы свежий архив улетал наружу сразу после создания. Если хочется инкрементальных снапшотов с дедупликацией и шифрованием при передаче — посмотрите restic на VPS: для CryptPad это особенно уместно, поскольку datastore растёт как append-only журнал, и дедупликация restic хорошо экономит трафик на повторных прогонах по сравнению с полным tar каждый раз. Разница между restic и его основным конкурентом разобрана в статье restic или BorgBackup — что выгоднее и когда, если решаете, что ставить.
Общие грабли с бэкапом Docker-томов — права доступа после восстановления, потерянные симлинки, незамеченные bind-mount мимо docker-compose.yml — разобраны в статье про бэкап Docker-томов на сервере, они актуальны и здесь, поскольку весь стейт CryptPad — это именно смонтированные тома.
Файл с ключами администратора храните отдельно от общего архива с данными пользователей — доступ к нему стоит ограничить сильнее, чем доступ к обычным бэкапам, которые иногда приходится расшаривать с подрядчиком для диагностики.
Восстановление и перенос на новый сервер
Восстановление на том же сервере после сбоя — разворачивание архива в исходные пути:
docker compose -f /app/cryptpad/docker-compose.yml stop cryptpad
rm -rf /app/cryptpad/{blob,block,data,datastore,customize,config}
tar -xzf /backup/cryptpad/cryptpad_2026-08-20.tar.gz -C /app/cryptpad
docker compose -f /app/cryptpad/docker-compose.yml start cryptpad
Проверьте владельца файлов после распаковки — если контейнер запускается от непривилегированного пользователя, права после tar -xzf от root могут не совпасть, и сервис не сможет писать в datastore:
docker compose -f /app/cryptpad/docker-compose.yml logs cryptpad --tail 50
# если видите ошибки EACCES/permission denied — поправьте владельца:
chown -R 4001:4001 /app/cryptpad/{blob,block,data,datastore}
(Точный UID зависит от версии образа — уточните его в docker-compose.yml, обычно он задан явно через user: либо виден в логах при старте контейнера.)
Перенос на новый VPS делается так же плюс дополнительные шаги: обновить DNS-запись на новый IP, проверить, что httpUnsafeOrigin/httpSafeOrigin в config.js указывают на правильный домен (несовпадение origin — частая причина, по которой браузер блокирует часть функциональности CryptPad из соображений same-origin), и перевыпустить SSL-сертификат для нового адреса — например, через Let's Encrypt на VPS, если раньше сертификат был привязан к старому серверу вручную. Если перед CryptPad стоит reverse-proxy — убедитесь, что он пробрасывает WebSocket: CryptPad использует его для realtime-синхронизации правок, и без корректных заголовков Upgrade/Connection документы будут открываться, но совместное редактирование в реальном времени сломается.
Частые ошибки после восстановления
| Симптом | Причина | Что проверить |
|---|---|---|
| Документ открывается, но выглядит пустым или «битым» | Восстановлена не вся связка datastore+data+block из одного и того же снимка, а вперемешку из разных дат | Все директории должны быть из одного архива/снапшота, снятого в одну сессию бэкапа |
| Пользователь не может войти под старым аккаунтом | Не восстановлена или повреждена директория block/ | Проверить, что block/ распакована полностью, права на запись у процесса CryptPad есть |
| Админ-панель недоступна или показывает «not authorized» | Ключи в adminKeys в config.js не совпадают с тем, что было до сбоя | Сверить adminKeys с сохранённым файлом admin-keys_*.txt, при необходимости прописать заново |
| Реалтайм-редактирование не работает, но пады открываются | Reverse-proxy на новом сервере не проксирует WebSocket | Добавить заголовки Upgrade/Connection: upgrade в конфиг nginx/Caddy перед CryptPad |
| Квоты пользователей посчитаны неверно после восстановления | Директория data/ (pin-store) устарела относительно datastore | Пересчитать квоты штатным скриптом CryptPad для пересборки pin-store, если он есть в вашей версии, либо принять временную неточность до следующего цикла активности пользователей |
Отдельно держите в голове: если пропала часть datastore, восстановить содержимое пада «по смыслу» невозможно в принципе — сервер никогда не видел расшифрованный текст, и без резервной копии именно этого файла данные теряются безвозвратно, в отличие от систем с обычной СУБД, где иногда можно вытащить остатки из WAL или бинлога.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверНужны сами нейросети для контента?
Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.
Частые вопросы
Можно ли расшифровать данные CryptPad на сервере, если потерян бэкап и нет ссылки на пад?
Нет. Шифрование end-to-end означает, что ключ расшифровки существует только в браузере пользователя (в URL после #) и никогда не передаётся и не хранится на сервере. Потеря и бэкапа, и пользовательской ссылки на пад означает безвозвратную потерю содержимого.
Нужно ли останавливать CryptPad для каждого бэкапа?
Строго не обязательно — журналы в datastore пишутся дозаписью, а не перезаписью поверх старых данных, поэтому риск порчи при tar на живых данных невысокий. Но для полной консистентности и предсказуемого поведения рекомендуется короткая остановка или снапшот файловой системы вместо tar.
Что произойдёт, если восстановить datastore, но забыть block?
Документы физически будут на месте, но зарегистрированные пользователи не смогут войти под своими аккаунтами тем же способом, что раньше — block отвечает именно за подтверждение входа без раскрытия пароля серверу.
Как часто снимать бэкап в проде с активной командой?
Ежедневного бэкапа обычно достаточно для большинства сценариев, но если для вас критична потеря даже нескольких часов правок, добавьте офсайт-синхронизацию через rclone/restic с более частым интервалом — она не мешает основному ночному циклу и стоит недорого по трафику при инкрементальном подходе.
Работает ли эта схема бэкапа, если CryptPad развёрнут не в Docker, а из исходников?
Да, принцип тот же — бэкапить нужно те же логические директории (blob, block, data, datastore, config, customize), просто их абсолютные пути на диске будут другими и их нужно смотреть в вашем config.js, а не в docker-compose.yml.
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →