Vaultwarden: бэкап и восстановление паролей
Свой Vaultwarden удобен ровно до того дня, когда сервер не поднимается. Бэкап здесь нестандартный: db.sqlite3 нельзя просто скопировать на ходу, вложения лежат вообще не в базе, а без rsa_key.pem все клиенты разом попросят логин заново. Разберём, что копировать, как снять горячую копию без остановки контейнера, чем шифровать и как убедиться, что восстановление действительно сработает.
Содержание
- Что лежит в каталоге данных и что из этого критично
- Почему `cp db.sqlite3` даёт битую копию
- Скрипт бэкапа: горячая копия, вложения, шифрование
- Автоматизация и вывоз копии за пределы сервера
- Восстановление: порядок действий и проверка
- Экспорт из клиента и то, чего бэкап не спасёт
- Какой сервер под Vaultwarden с бэкапами взять в MAATRIX
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →Что лежит в каталоге данных и что из этого критично
Vaultwarden хранит всё в одном каталоге — /data внутри контейнера, обычно смонтированном в /opt/vaultwarden/data; для нативной установки путь задаёт переменная DATA_FOLDER. Команда du -sh /opt/vaultwarden/data/* | sort -h на семейном инстансе с 300–400 записями даёт примерно такое:
2.8M /opt/vaultwarden/data/db.sqlite3
19M /opt/vaultwarden/data/attachments
41M /opt/vaultwarden/data/sends
187M /opt/vaultwarden/data/icon_cache
Пропорции показательные: сама база — три мегабайта, а больше половины каталога занимает мусор, который восстанавливать не нужно.
| Путь | Что внутри | Бэкапить |
|---|---|---|
db.sqlite3 | пользователи, записи, папки, организации, метаданные вложений | обязательно, только горячей копией |
db.sqlite3-wal, db.sqlite3-shm | журнал WAL и разделяемая память | нет, это состояние живой БД |
attachments/ | зашифрованные файлы-вложения | обязательно, в базе их нет |
sends/ | файлы Bitwarden Send | да, если Send используется |
rsa_key.pem | приватный ключ подписи JWT | обязательно |
config.json | настройки, сделанные через /admin | обязательно |
icon_cache/ | скачанные фавиконки сайтов | нет, отрастёт сам |
tmp/ | временные файлы загрузок | нет |
В старых установках рядом лежат rsa_key.der и rsa_key.pub.der — забирайте всё, что начинается с rsa_key. Потеря этого файла не уничтожает пароли, хранилище зашифровано ключом от мастер-пароля, но обнуляет все выданные токены: каждый клиент попросит вход заново вместе со вторым фактором.
И то, о чём забывают почти все: каталог данных без .env — это половина бэкапа. В .env и docker-compose.yml живут ADMIN_TOKEN, DOMAIN, настройки SMTP и пара PUSH_INSTALLATION_ID / PUSH_INSTALLATION_KEY. Восстановив только data, вы получите хранилище без писем, без пушей и без входа в админку.
Почему `cp db.sqlite3` даёт битую копию
Vaultwarden держит SQLite в режиме WAL — переменная ENABLE_DB_WAL по умолчанию true. Свежие транзакции какое-то время живут не в db.sqlite3, а в файле db.sqlite3-wal. Отсюда два способа испортить себе бэкап.
Скопировали только основной файл — получили базу без последних изменений: пароли, добавленные за минувшие часы, в копии просто отсутствуют, и заметите вы это уже после восстановления.
Скопировали все три файла на ходу — получили несогласованный набор: cp читает мегабайты не мгновенно, и конец файла относится к другому моменту, чем начало. SQLite такое обычно распознаёт, и при первом обращении вы увидите одно из:
Error: database disk image is malformed
Error: file is not a database
Error: near line 1: database is locked
Правильный способ — встроенный онлайн-бэкап SQLite: он берёт корректные блокировки и отдаёт один цельный файл, в который WAL уже влит.
sqlite3 /opt/vaultwarden/data/db.sqlite3 ".backup '/var/backups/vaultwarden/db.sqlite3'"
Контейнер останавливать не нужно — на базе в несколько мегабайт команда отрабатывает меньше чем за секунду. Альтернатива — VACUUM INTO: она дополнительно сжимает базу, но откажется писать поверх существующего файла с Error: output file already exists.
Сделать это «изнутри» контейнера не выйдет:
OCI runtime exec failed: exec: "sqlite3": executable file not found in $PATH: unknown
Образ vaultwarden/server минимальный, консольного клиента SQLite в нём нет. Ставьте его на хост (apt install -y sqlite3) и работайте с примонтированным каталогом напрямую: файл тот же самый, блокировки общие для всех процессов.
Копия без проверки бэкапом не считается. Две команды сразу после снятия:
sqlite3 /var/backups/vaultwarden/db.sqlite3 'PRAGMA integrity_check;' # ждём ровно: ok
sqlite3 /var/backups/vaultwarden/db.sqlite3 \
'select (select count(*) from users) u, (select count(*) from ciphers) c,
(select count(*) from attachments) a;'
Запомните эти три числа: если завтра ciphers уменьшится вдвое, вы узнаете о проблеме до восстановления, а не во время.
Развернуть за пару минут
Готовый образ на VPS MAATRIX: NVMe, AMD EPYC, root-доступ. Локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Развернуть VaultwardenСкрипт бэкапа: горячая копия, вложения, шифрование
Логика простая: снять согласованную копию базы в промежуточный каталог, положить туда же мелкие критичные файлы, а вложения и Send забрать прямо из каталога данных.
#!/usr/bin/env bash
set -Eeuo pipefail
DATA=/opt/vaultwarden/data
APP=/opt/vaultwarden
STAGE=/var/backups/vaultwarden
install -d -m 700 "$STAGE"
rm -f "$STAGE/db.sqlite3"
sqlite3 "$DATA/db.sqlite3" ".backup '$STAGE/db.sqlite3'"
sqlite3 "$STAGE/db.sqlite3" 'PRAGMA integrity_check;' | grep -qx ok
cp -a "$DATA"/rsa_key.* "$DATA/config.json" "$STAGE/" 2>/dev/null || true
cp -a "$APP/.env" "$APP/docker-compose.yml" "$STAGE/" 2>/dev/null || true
export RESTIC_REPOSITORY="s3:s3.eu-central-003.backblazeb2.com/vw-backup"
export RESTIC_PASSWORD_FILE=/root/.restic-pass
restic backup --tag vaultwarden "$STAGE" "$DATA/attachments" "$DATA/sends"
restic forget --tag vaultwarden \
--keep-daily 7 --keep-weekly 4 --keep-monthly 6 --prune
Строка с grep -qx ok не косметика: при set -e скрипт остановится, если integrity_check вернул что угодно кроме ok, и заведомо мёртвая копия в репозиторий не уедет. Каталог icon_cache в список не попал — те самые 187 МБ мусора, которые незачем гонять по сети каждую ночь.
Шифрование здесь не паранойя. В db.sqlite3 лежат адреса почты всех пользователей, Argon2-хеши мастер-паролей, зашифрованные ключи организаций и полная структура хранилища. Плюс rsa_key.pem, которым подписываются токены: имея его и копию базы, можно поднять свой Vaultwarden и разбирать чужие данные офлайн, без спешки. Поэтому копия либо в restic (там AES-256 включён всегда и отключить его нельзя), либо через age: tar -C /var/backups -cf - vaultwarden | age -R /root/backup-key.pub > vw-$(date -u +%F).tar.age.
Пароль от репозитория и приватный ключ age держите вне этого сервера. Классическая ловушка: единственная копия пароля от бэкапа хранится в том самом Vaultwarden, который вы восстанавливаете. Остальные грабли шифрованных копий — потерянный ключ, забытый --password-file, репозиторий без проверки — разобраны отдельно в материале бэкап с шифрованием: частые ошибки.
Если инстанс переведён на PostgreSQL через DATABASE_URL, база снимается иначе, а вложения — так же, они всё равно на диске:
docker exec -t vw-db pg_dump -U vaultwarden -Fc vaultwarden > "$STAGE/db.dump"
Автоматизация и вывоз копии за пределы сервера
Ручной бэкап живёт недели три. Дальше — таймер systemd: он пишет в журнал, догоняет пропущенные запуски и не зависит от почты root.
# /etc/systemd/system/vaultwarden-backup.service
[Service]
Type=oneshot
ExecStart=/usr/local/sbin/vw-backup.sh
Nice=10
IOSchedulingClass=idle
# /etc/systemd/system/vaultwarden-backup.timer
[Timer]
OnCalendar=*-*-* 03:30:00
RandomizedDelaySec=15m
Persistent=true
[Install]
WantedBy=timers.target
systemctl daemon-reload
systemctl enable --now vaultwarden-backup.timer
systemctl list-timers vaultwarden-backup.timer
journalctl -u vaultwarden-backup.service -n 50 --no-pager
Persistent=true запустит задание после включения сервера, если в 03:30 он был выключен. RandomizedDelaySec=15m разводит нагрузку, когда серверов несколько.
Тем, кто остался на cron, — про главную грабли. Cron даёт урезанный PATH и не читает ваш профиль, поэтому переменные restic до скрипта не доезжают, и в почте вы находите Fatal: Please specify repository location (-r or --repository-file). Лечится явным экспортом переменных внутри самого скрипта (как в примере выше) и абсолютными путями к бинарникам.
Копия обязана лежать не на этом сервере. Диск умирает вместе с хранилищем, а неудачный rm -rf уносит и данные, и бэкап рядом. Варианты: второй VPS в другой локации (что под это брать — в разборе VPS для бэкапов и архива), объектное хранилище S3/B2, домашний NAS через rclone. Честный риск: если сервер скомпрометирован, ключ restic из его памяти позволяет стереть и удалённый репозиторий — спасает только rest-server --append-only на приёмнике или ключ приложения B2 без права удаления.
Молчаливо сломавшийся бэкап хуже отсутствующего. Добавьте в конец скрипта пинг в мониторинг — curl -fsS -m 10 https://hc-ping.com/<uuid> — и узнаете о проблеме наутро, а не через полгода.
Восстановление: порядок действий и проверка
Порядок важен: сначала остановить сервис, потом трогать файлы. Копирование базы под работающим Vaultwarden даёт ту же несогласованность, только теперь в боевых данных.
cd /opt/vaultwarden && docker compose down
mv data data.broken
restic restore latest --tag vaultwarden --target /tmp/vw-restore
S=/tmp/vw-restore/var/backups/vaultwarden
install -d -m 750 data
cp -a "$S"/db.sqlite3 "$S"/rsa_key.* "$S"/config.json data/
cp -a /tmp/vw-restore/opt/vaultwarden/data/{attachments,sends} data/
rm -f data/db.sqlite3-wal data/db.sqlite3-shm
chown -R root:root data
docker compose up -d
Две строки чаще всего пропускают. Первая — удаление -wal и -shm: восстановленная через .backup база самодостаточна, а журнал от прежней установки здесь лишний. Вторая — chown. Официальный образ работает от root, и если архив распаковывали из-под обычного пользователя, контейнер стартует и сразу ляжет, а в docker compose logs vaultwarden окажется unable to open database file. Этот и соседние симптомы после восстановления — readonly database, 502 на прокси, пустая /admin — разобраны в статье Vaultwarden на сервере: частые ошибки.
Успешный старт выглядит однозначно: в логе появляется строка вида Rocket has launched from http://0.0.0.0:80, а curl -s http://127.0.0.1:8080/alive возвращает текущее время в UTC.
Дальше — сверка вложений. В базе и на диске их должно быть поровну:
sqlite3 /opt/vaultwarden/data/db.sqlite3 'select count(*) from attachments;'
find /opt/vaultwarden/data/attachments -type f | wc -l
Расхождение значит, что записи в хранилище есть, а файлы под ними отдадут 404 при попытке скачать.
Главное правило: бэкап, который ни разу не восстанавливали, бэкапом не является. Раз в квартал разворачивайте копию в песочнице на отдельном порту и входите в неё браузером:
docker run --rm -p 8080:80 -v /srv/vw-test/data:/data \
-e DOMAIN=http://localhost:8080 -e PUSH_ENABLED=false \
vaultwarden/server:1.34.3
Три детали, каждая из которых меняет результат проверки. DOMAIN обязательно свой, иначе тестовый инстанс начнёт слать письма и пуши от имени боевого; PUSH_ENABLED=false страхует от того же. И тег образа берите тот же, что в проде, а не latest: свежая версия прогонит миграции по восстановленной базе, и вы проверите не свою копию, а её обновлённый вариант. Узнать текущий тег: docker inspect --format '{{.Config.Image}}' vaultwarden. Пять минут — и вы знаете ответ на единственный важный вопрос: открываются ли пароли.
Экспорт из клиента и то, чего бэкап не спасёт
Серверная копия решает не все задачи, и границы стоит очертить заранее.
Экспорт из веб-хранилища даёт файл, читаемый без Vaultwarden вообще, но у него три ограничения. Вложения в экспорт не попадают — ни в JSON, ни в CSV. Записи организаций выгружаются отдельно и только владельцем. И самое коварное — выбор формата: вариант «привязанный к аккаунту» зашифрован ключом вашей учётной записи, и импортировать его в новый аккаунт после потери мастер-пароля невозможно. Для офлайн-копии годится только «защищённый паролем», где парольную фразу задаёте вы, — либо нешифрованный JSON, который в ту же секунду становится файлом с паролями открытым текстом и жить на диске не должен.
Забытый мастер-пароль не восстанавливается ничем. Vaultwarden его не хранит и расшифровать хранилище не может: страница /admin умеет только удалить пользователя вместе с данными. Бэкап здесь бесполезен — вы восстановите тот же нечитаемый контейнер. Работает лишь подготовка: подсказка к паролю (нужен настроенный SMTP) и Emergency Access, где доверенный контакт через заданный период ожидания получает право на takeover.
Двухфакторка отдельно. Если TOTP-код от самого Vaultwarden лежит внутри Vaultwarden, при переезде вы получите замкнутый круг. Держите резервные коды 2FA вне хранилища; в крайнем случае второй фактор снимает администратор через /admin.
Обновления и откаты. Миграции схемы односторонние: после запуска новой версии база обновляется, и вернуть старый тег образа уже нельзя — сервер откажется работать с более новой схемой. Перед docker compose pull снимайте копию базы отдельной командой: тридцать секунд и единственный путь назад.
Какой сервер под Vaultwarden с бэкапами взять в MAATRIX
Vaultwarden написан на Rust и в простое почти ничего не ест: docker stats показывает 55–80 МиБ на контейнер при десятке пользователей. Ресурсы забирает не он, а TLS-терминация, база и особенно обслуживание бэкапов.
Минимум: 1 vCPU, 1 ГБ RAM, 20 ГБ NVMe. Хватит на семью или команду до 10–15 человек с базой в несколько мегабайт. Честное ограничение: на 1 ГБ операция restic prune по репозиторию с гигабайтами вложений упирается в память и рискует быть убитой по OOM — там либо --max-repack-size 1G, либо ежедневный forget без prune и полная чистка раз в месяц. Локальный кеш restic в /root/.cache/restic тоже отъедает сотни мегабайт диска — при 20 ГБ это заметно.
Комфортный вариант: 2 vCPU, 2–4 ГБ RAM, 40 ГБ NVMe. Здесь помещаются Vaultwarden, Nginx или Caddy с сертификатом, PostgreSQL вместо SQLite, если хранилище растёт, вложения на десятки гигабайт и, главное, тестовый инстанс для проверки восстановления рядом с боевым.
Локация — Великобритания (Лондон). Менеджер паролей — это персональные данные сотрудников, и для команды, работающей с европейскими контрагентами, соседство с GDPR-периметром и предсказуемая юрисдикция важнее пары миллисекунд. Пинг из Западной Европы — 10–30 мс, из Москвы до Лондона порядка 45–60 мс, для хранилища паролей это незаметно. Если аудитория целиком российская и вы подпадаете под 152-ФЗ, берите RU; если основной поток из Франции и Бенилюкса — FR. И правило, ради которого всё затевалось: бэкап держите не в той же локации — сервер в UK, репозиторий restic в US или во внешнем S3.
Приложение из каталога apps.maatrix.io ставится на сервер автоматически при заказе — вставлять команды руками не нужно, автоустановка работает на Ubuntu и Debian. Доступы к готовому Vaultwarden появляются в личном кабинете, в разделе «Доступ»; вам останется завести первого пользователя, закрыть регистрацию через SIGNUPS_ALLOWED=false и в первый же вечер поставить таймер бэкапа из этой статьи. Если разворачиваете вручную и хотите пройти путь с нуля — порядок действий в материале как установить и настроить Vaultwarden на VPS, конфигурации и локации — на странице аренды VPS. Оплата — картой российского банка, по СБП, криптовалютой или токеном MAAT; зарубежная карта для британской локации не нужна.
Развернуть за пару минут
Готовый образ на VPS MAATRIX: NVMe, AMD EPYC, root-доступ. Локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Развернуть VaultwardenОбсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →Частые вопросы
Можно ли делать бэкап, не останавливая контейнер?
Да, если снимать базу командой sqlite3 db.sqlite3 ".backup '...'" — она берёт корректные блокировки и отдаёт согласованный файл. А вот cp или tar по живому каталогу дают битую копию: часть транзакций остаётся в db.sqlite3-wal.
Что будет, если восстановить базу без rsa_key.pem?
Пароли не пострадают — хранилище зашифровано ключом пользователя. Но Vaultwarden сгенерирует новый ключ подписи, все выданные токены станут невалидными, и каждое устройство попросит вход заново вместе со вторым фактором.
Я потерял мастер-пароль. Поможет ли бэкап сервера?
Нет. Данные зашифрованы на клиенте, и в копии лежит тот же нечитаемый контейнер; страница /admin умеет только удалить пользователя. Спасает лишь заранее настроенный Emergency Access или отдельно сохранённый экспорт «защищённый паролем».
Нужны сами нейросети для контента?
Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.