Как правильно сделать резервную копию Vaultwarden на VPS
Используйте команду sqlite3 .backup для создания корректного дампа базы данных. Узнайте, как сохранить файлы attachments, config.json и rsa_key для полного восстановления.
Что должен содержать резервный архив Vaultwarden
Резервная копия Vaultwarden — это копия всей папки с данными, при этом база данных внутри неё должна быть скопирована корректно. Используйте sqlite3 db.sqlite3 ".backup out.sqlite3" вместо cp, так как простое копирование базы данных в момент записи может привести к получению повреждённого файла, который невозможно открыть. Затем сохраните файлы, расположенные рядом с ней; это та часть, о которой часто забывают.
При установке через Docker папка с данными — это то, что вы примонтировали в /data. Это может быть путь на хосте или именованный том, а разница между bind mounts и именованными томами определяет, где именно на диске физически находятся данные вашего хранилища. Вот что они включают:
db.sqlite3: все учётные записи, все элементы хранилища, все папки и все организации. Потеря этого файла означает потерю хранилища.db.sqlite3-walиdb.sqlite3-shm: журнал предзаписи (WAL) и его индекс в разделяемой памяти. Недавние записи хранятся здесь, пока SQLite не перенесёт их в основной файл.attachments/: файлы, прикреплённые пользователями к элементам хранилища; они зашифрованы и распределены по одной директории на каждый элемент.sends/: файлы, стоящие за ссылками Bitwarden Send.config.json: все настройки, сохранённые вами на странице администратора.rsa_key.pem, а такжеrsa_key.derиrsa_key.pub.derв старых версиях: ключ, подписывающий токены входа.icon_cache/: загруженные иконки веб-сайтов. Это единственная директория, которую можно пропустить, так как Vaultwarden загрузит их снова по мере необходимости.
Безопасна ли моя база данных Vaultwarden? Что на самом деле содержится в файле
На этот вопрос отвечают две команды, и вы можете запустить обе прямо сейчас.
sudo apt update && sudo apt install -y sqlite3
sudo sqlite3 /opt/vaultwarden/data/db.sqlite3 "select email from users;"
sudo sqlite3 /opt/vaultwarden/data/db.sqlite3 "select name from ciphers limit 1;"Первая команда выводит адреса электронной почты ваших пользователей в открытом виде. Вторая выводит название одного элемента, и выглядит это так:
2.k9Qw1nQ0y7Yy2Xw==|E1r0J3l5s7d9f1g3h5j7k9==|Lm4nOp6qRs8tUv0wXy2zAb4cDe6fGh8i=Названия элементов, имена пользователей, пароли и заметки шифруются клиентом до отправки, поэтому сервер хранит только зашифрованный текст, который он не может прочитать. Префикс 2. — это тип шифрования Bitwarden, за которым следуют вектор инициализации (IV), зашифрованный текст и MAC (код аутентификации сообщения), каждый из которых представлен в формате base64 и разделен символом |. Ключ для расшифровки выводится из мастер-пароля учетной записи, который никогда не передается на сервер в пригодном для использования виде. Эта часть идентична независимо от того, используете ли вы Vaultwarden или официальный сервер, как подробно описано в сравнении Vaultwarden и self-hosted Bitwarden.
Остальная часть базы данных не зашифрована. Адреса электронной почты, имена учетных записей, подсказки к паролям и коды восстановления двухфакторной аутентификации хранятся в виде обычного текста рядом с метаданными, такими как время создания и принадлежность элемента к организации. Таким образом, сам файл резервной копии является секретным. Любой, кто завладеет им, узнает, кто ваши пользователи, и сможет атаковать зашифрованные блоки в офлайн-режиме с любой скоростью, которую позволяет их оборудование. Этот факт диктует правила хранения, описанные ниже: копия должна быть зашифрована до того, как покинет сервер. Токен администратора — это вторая половина той же проблемы, и в руководстве по усилению защиты self-hosted Vaultwarden подробно рассматриваются оба аспекта.
Почему копирование db.sqlite3 во время работы Vaultwarden не является резервной копией
Vaultwarden по умолчанию использует SQLite в режиме WAL (ENABLE_DB_WAL=true). Запись сначала попадает в db.sqlite3-wal, и только после контрольной точки (checkpoint) данные переносятся в db.sqlite3. Если скопировать только db.sqlite3, вы получите состояние базы данных на момент последней контрольной точки. В результате пароль, сохраненный десять минут назад, может отсутствовать в архиве, и система не выдаст никаких предупреждений.
Копирование всех трех файлов с помощью cp также не решает проблему. Копии создаются с небольшой разницей во времени, поэтому сохраненный WAL может описывать версии страниц, которые уже не соответствуют основному файлу. В итоге SQLite пытается восстановить один файл из другого, что приводит к повреждению данных. Вы узнаете об этом гораздо позже:
Error: database disk image is malformed.backup позволяет избежать этой проблемы, так как использует Online Backup API от SQLite. Документация SQLite рекомендует этот метод для копирования базы данных, которая может активно использоваться. Утилита считывает страницы под блокировкой на чтение и перезапускает процесс, если в это время происходит запись, поэтому на диск записывается консистентный снимок данных на конкретный момент времени.
Создание копии базы данных с помощью sqlite3 .backup
sudo apt update && sudo apt install -y sqlite3
sudo install -d -m 700 /var/backups/vaultwarden
OUT=/var/backups/vaultwarden/db-$(date '+%Y%m%d-%H%M').sqlite3
sudo sqlite3 /opt/vaultwarden/data/db.sqlite3 ".backup '$OUT'"
sudo sqlite3 "$OUT" "PRAGMA integrity_check;"Последняя команда выводит ok отдельной строкой. Любой другой результат означает, что копия непригодна для использования: не сохраняйте её и не удаляйте предыдущую версию. Вся последовательность выполняется на работающем сервере, поэтому сеансы пользователей не прерываются, а контейнеры не перезапускаются.
Инструмент sqlite3 отсутствует внутри контейнера Vaultwarden. Образ собран на debian:trixie-slim с использованием ca-certificates, curl, libmariadb3, libpq5 и openssl, поэтому docker exec vaultwarden sqlite3 ... завершается ошибкой:
exec: "sqlite3": executable file not found in $PATHВместо этого запустите команду на хосте, указав путь к смонтированной директории, как показано в примерах выше. Если данные хранятся в именованном томе (named volume), docker volume inspect <name> выведет путь к нему на хосте в поле /var/lib/docker/volumes/.
Начиная с версии 1.32.1, в Vaultwarden встроена собственная команда для резервного копирования. На вашем сервере:
docker exec -it vaultwarden /vaultwarden backupОна запускает VACUUM INTO и записывает db_YYYYMMDD_HHMMSS.sqlite3 в папку с данными. Учтите два момента. Копия сохраняется рядом с оригиналом на том же диске, поэтому это лишь промежуточный этап, а не полноценный бэкап. Кроме того, функция работает только с SQLite: при использовании MariaDB или PostgreSQL выполнение прерывается с сообщением The database type is not SQLite. Backups only works for SQLite databases.
Файлы, о которых часто забывают
attachments/ содержит зашифрованные данные под непрозрачными именами. Запись в базе данных для каждого вложения содержит его зашифрованное имя файла и ключевой материал, необходимый клиенту для расшифровки. Вложения без базы данных — это нечитаемый набор байтов, а база данных без вложений приводит к тому, что загрузка файлов пользователями завершается ошибкой. Создавайте резервные копии обоих компонентов одновременно.
config.json хранит все настройки, сохранённые через панель администратора, и их значения имеют приоритет над соответствующими переменными окружения. Это работает в обе стороны: восстановление старого config.json незаметно переопределяет настройки в вашем compose-файле, а сам файл является конфиденциальным, так как может содержать ваш SMTP-пароль и токен администратора. Храните этот токен в виде строки Argon2id PHC (password hashing competition), а не в открытом виде. docker run --rm -it vaultwarden/server /vaultwarden hash может сгенерировать такую строку для вас.
rsa_key.pem используется для подписи JSON web tokens (JWT), которые поддерживают сессии пользователей. Если файл отсутствует при запуске, Vaultwarden генерирует новый ключ, из-за чего все токены, подписанные старым ключом, перестают проходить проверку, и все клиенты принудительно выходят из системы. Содержимое хранилища при этом сохраняется, так как оно зашифровано ключами, производными от мастер-пароля. Восстановление файла ключа позволяет избежать массового выхода пользователей из системы.
sends/ содержит файлы, стоящие за ссылками Send. Их отсутствие приводит только к невозможности загрузки этих файлов, на остальную функциональность это не влияет.
Объединение всего в один скрипт
#!/bin/bash
set -euo pipefail
DATA=/opt/vaultwarden/data
DEST=/var/backups/vaultwarden
STAMP=$(date '+%Y%m%d-%H%M%S')
STAGE=$(mktemp -d /tmp/vw-stage.XXXXXX)
install -d -m 700 "$DEST"
sqlite3 "$DATA/db.sqlite3" ".backup '$STAGE/db.sqlite3'"
test "$(sqlite3 "$STAGE/db.sqlite3" 'PRAGMA integrity_check;')" = "ok"
cp -a "$DATA"/rsa_key* "$STAGE/"
for extra in config.json attachments sends; do
if [ -e "$DATA/$extra" ]; then cp -a "$DATA/$extra" "$STAGE/"; fi
done
tar -C "$STAGE" -czf "$DEST/vw-$STAMP.tar.gz" .
chmod 600 "$DEST/vw-$STAMP.tar.gz"
rm -rf "$STAGE"
tar -tzf "$DEST/vw-$STAMP.tar.gz"Сохраните код в файл /usr/local/sbin/vw-backup.sh, сделайте его исполняемым с помощью chmod 700 и запустите от имени root. Строка test выполняет основную работу: sqlite3 возвращает код завершения 0 даже в том случае, если PRAGMA integrity_check сообщает о повреждении данных, поэтому сравнение вывода с ok позволяет превратить создание некорректной копии в ошибку выполнения скрипта. Команда set -euo pipefail в этом случае останавливает процесс, не позволяя tar создать архив с поврежденной базой данных.
Финальная команда tar -tzf выводит список того, что было успешно сохранено. Проверьте этот список при первом запуске. Вы должны увидеть ./db.sqlite3, ./rsa_key.pem, ./config.json и ./attachments/, при этом ./db.sqlite3-wal должен отсутствовать. Запускайте скрипт каждую ночь через systemd service и timer, а не через cron, если вам нужны journalctl логи и юнит, который сообщает о сбоях.
Проверка резервной копии путем восстановления во временный каталог
Непроверенная резервная копия — это лишь предположение. Восстановление во временный каталог занимает минуту и не затрагивает рабочие данные.
sudo install -d -m 700 /tmp/vw-check
sudo tar -C /tmp/vw-check -xzf /var/backups/vaultwarden/vw-20260805-030000.tar.gz
ls -l /tmp/vw-check
sudo sqlite3 /tmp/vw-check/db.sqlite3 "PRAGMA integrity_check;"
sudo sqlite3 /tmp/vw-check/db.sqlite3 "select count(*) from users;"
sudo sqlite3 /tmp/vw-check/db.sqlite3 "select count(*) from ciphers;"
sudo du -sh /tmp/vw-check/attachmentsВажны четыре результата. integrity_check выводит ok. Количество пользователей совпадает с числом известных вам учетных записей. Количество шифров близко к значению из работающей системы sudo sqlite3 /opt/vaultwarden/data/db.sqlite3 "select count(*) from ciphers;" и никогда не равно нулю для используемого хранилища. Размер каталога вложений примерно соответствует ожидаемому (этот шаг можно пропустить, если никто не загружает вложения). Затем запустите sudo rm -rf /tmp/vw-check, так как в этом каталоге теперь находится вторая копия всех данных.
При восстановлении любой скопированной вручную папки данных действует одно правило: перед запуском сервера удалите db.sqlite3-wal и db.sqlite3-shm. В противном случае SQLite попытается восстановить базу данных, используя журнал, относящийся к другой ее копии, что приведет к повреждению изначально целостной базы. Архивы, созданные с помощью приведенного выше скрипта, никогда не содержат этих файлов, так как .backup записывает одну полную базу данных.
Восстановление данных на сервере
Эти операции выполняются на вашем сервере при остановленном контейнере. Vaultwarden не должен записывать данные в момент изменения содержимого папки с данными.
cd /opt/vaultwarden
docker compose stop vaultwarden
sudo mv data data.old.$(date '+%Y%m%d-%H%M%S')
sudo install -d -m 700 data
sudo tar -C data -xzf /var/backups/vaultwarden/vw-20260805-030000.tar.gz
sudo chown -R root:root data
docker compose start vaultwarden
docker compose logs --tail 20 vaultwardenВ chown необходимо указать пользователя, от имени которого работает контейнер. Стандартный образ работает под root, поэтому root:root подходит, если только вы не задали user: в файле compose — в этом случае используйте соответствующие uid и gid. Если у сервера нет прав на запись в папку с данными, страница входа будет отклонять все запросы, о чем будет сообщено в логах.
Успешный запуск завершается строкой Rocket:
[INFO] Rocket has launched from http://0.0.0.0:80После этого войдите в систему через браузер, откройте элемент и загрузите одно вложение. Если вход в систему работает, а загрузка вложений завершается ошибкой, это означает, что в архиве была база данных, но отсутствовала папка attachments/. Сохраняйте data.old.* до тех пор, пока не убедитесь в корректности всех данных, после чего удалите её. Откат к предыдущему состоянию выполняется аналогично, в три этапа, с заменой директорий местами.
Если ваши пути отличаются от указанных здесь, руководство по установке Vaultwarden на VPS содержит файл compose, на который опираются данные команды.
Где не следует хранить резервные копии
- Не на том же диске, где находится папка с данными. Один вышедший из строя том уничтожит обе копии, как и одна ошибка
rm -rfв пути. - Не на том же сервере, даже на втором томе. Злоумышленник, получивший доступ к root, получит доступ и к вашим резервным копиям в рамках той же сессии.
- Не в объектном хранилище без шифрования, так как архив содержит адреса электронной почты, подсказки к паролям, коды восстановления и зашифрованные данные хранилища, которые можно подвергнуть офлайн-атаке.
- Не только в снимках (snapshots) вашего провайдера. Они быстро восстанавливаются, что полезно, но они находятся в той же учетной записи, что и сервер, поэтому проблема с доступом к аккаунту приведет к их потере.
Удаленная копия — это то, для чего подходит restic, поскольку репозиторий restic шифруется на машине до начала загрузки. На вашем сервере:
sudo apt install -y restic
export RESTIC_REPOSITORY=s3:https://s3.example.com/vaultwarden-backups
export RESTIC_PASSWORD_FILE=/root/.restic-password
restic init
restic backup /var/backups/vaultwarden --tag vaultwarden
restic snapshots --tag vaultwarden
restic forget --tag vaultwarden --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --pruneУказывайте restic на директорию с архивом, а не на папку с рабочими данными, чтобы загружалась именно та согласованная копия, которую вы уже проверили. Храните пароль от репозитория где угодно, кроме самого сервера, который он защищает: если вы потеряете этот пароль, снимки станут нечитаемыми — так задумано архитектурой. Там, где хранилище это поддерживает, предоставьте серверу учетные данные с правами на запись, но без прав на удаление, чтобы взлом системы не позволил ей стереть собственную историю. Настройка резервного копирования с помощью restic на VPS полностью описывает работу с репозиторием и расписанием, а сравнение restic и BorgBackup поможет с выбором, если вы его еще не сделали.
Тестирование восстановления по расписанию
Выберите один день в месяц. Извлеките самый свежий снапшот во временную директорию с помощью restic restore latest --tag vaultwarden --target /tmp/vw-check, выполните те же PRAGMA integrity_check, проверьте количество строк и запишите дату и полученные значения. Резервная копия, которую никто не восстанавливал в течение шести месяцев, — это копия с неизвестным состоянием. Вы узнаете её состояние во время сбоя, что является худшим моментом для подобных открытий.
Раз в год проводите полную проверку. Запустите второй контейнер Vaultwarden на свободном порту с восстановленной папкой данных и войдите в систему под реальной учётной записью. Это подтвердит работоспособность пути мастер-пароля от начала до конца, чего не может сделать простая проверка количества строк. restic check --read-data-subset=10% по тому же расписанию подтверждает, что сохранённые данные можно прочитать, а не просто увидеть в списке.
FAQ
Можно ли копировать db.sqlite3 командой cp во время работы Vaultwarden?
Нет. Vaultwarden использует SQLite в режиме WAL, поэтому последние записи находятся в db.sqlite3-wal и еще не перенесены в db.sqlite3. cp только основного файла приведет к их потере, а раздельное копирование двух файлов может создать несогласованную пару, что позже проявится как Error: database disk image is malformed. Используйте sqlite3 /path/db.sqlite3 ".backup '/path/out.sqlite3'". Этот инструмент задействует Online Backup API от SQLite и создает один согласованный файл без остановки сервера.
Нужно ли останавливать контейнер Vaultwarden для создания резервной копии?
Нет, в этом и заключается смысл .backup. Копия базы данных безопасна при работающем сервере. Вложения и файлы Send записываются в момент загрузки пользователем, поэтому файл, добавленный между копированием базы данных и tar, может не попасть в ночной архив, что в худшем случае приведет к потере одного вложения. Если несколько секунд простоя не критичны, docker compose stop перед скриптом и docker compose start после него исключат даже этот риск.
Что произойдет при восстановлении без файлов rsa_key?
Vaultwarden сгенерирует новый ключ при запуске. Этот ключ подписывает JSON web tokens (JWT), которые поддерживают сессии, поэтому все существующие токены перестанут проходить проверку, все клиенты будут принудительно разлогинены и должны будут войти снова. Содержимое хранилищ не пострадает, так как оно зашифровано ключами, производными от мастер-пароля каждого пользователя, а не RSA-ключом. Восстанавливайте rsa_key.pem вместе с остальными данными из папки, и пользователи не заметят процесса восстановления.
Безопасно ли загружать архив резервной копии в объектное хранилище в исходном виде?
Нет. Имена элементов, пароли и заметки зашифрованы, но адреса электронной почты, имена учетных записей, подсказки к паролям и коды восстановления двухфакторной аутентификации хранятся в базе данных в открытом виде. Злоумышленник, получивший доступ к архиву, сможет расшифровывать данные в автономном режиме с любой скоростью. Шифруйте архив до того, как он покинет сервер. Репозиторий restic делает это автоматически, а gpg --symmetric --cipher-algo AES256 vw-20260805-030000.tar.gz создает единый зашифрованный файл, который можно передать в любое хранилище.
Как выполнять резервное копирование Vaultwarden на PostgreSQL или MariaDB?
Инструкции для SQLite здесь не применимы, а встроенная команда выдаст ошибку The database type is not SQLite. Backups only works for SQLite databases. Создавайте дамп базы данных с помощью штатных инструментов, pg_dump или mysqldump, при этом все остальные правила остаются в силе. Дамп должен находиться в одном архиве с attachments/, sends/, config.json и файлами rsa_key. Все данные должны быть собраны за один проход, зашифрованы и сохранены за пределами сервера, на котором они были созданы.