SSD Nodes Learn Hosting plans →
Przewodniki Matt ConnorAutor: Matt Connor · Zaktualizowano 2026-08-28

Jak wykonać kopię zapasową i przywrócić Vaultwarden

Dowiedz się, jak poprawnie zabezpieczyć bazę danych SQLite przy użyciu polecenia .backup. Zapewnij integralność plików attachments, config.json oraz rsa_key na serwerze VPS.

Verified Every command ran end-to-end on a fresh Ubuntu 24.04 server, August 5, 2026.

Co musi zawierać kopia zapasowa Vaultwarden

Kopia zapasowa Vaultwarden to kopia całego folderu danych, przy czym baza danych wewnątrz musi zostać skopiowana w odpowiedni sposób. Należy uruchomić sqlite3 db.sqlite3 ".backup out.sqlite3" zamiast cp, ponieważ zwykłe kopiowanie bazy danych w trakcie zapisu może skutkować plikiem, którego nie da się otworzyć. Następnie należy zachować pliki znajdujące się obok niej; jest to połowa procesu, o której użytkownicy często zapominają.

W instalacji Docker folder danych to lokalizacja zamontowana w /data. Jest to ścieżka na hoście lub wolumen nazwany, a różnica między bind mounts a wolumenami nazwanymi decyduje o tym, gdzie fizycznie na dysku znajduje się sejf. Oto co zawiera ten folder:

  • db.sqlite3: każde konto, każdy element sejfu, każdy folder i każda organizacja. Utrata tego pliku oznacza utratę sejfu.
  • db.sqlite3-wal oraz db.sqlite3-shm: dziennik transakcji (WAL) i jego indeks pamięci współdzielonej. Ostatnie zapisy znajdują się tutaj, dopóki SQLite nie scali ich z głównym plikiem.
  • attachments/: pliki załączone przez użytkowników do elementów sejfu, zaszyfrowane, w jednym katalogu na element.
  • sends/: pliki powiązane z linkami Bitwarden Send.
  • config.json: wszystkie ustawienia zapisane z poziomu strony administratora.
  • rsa_key.pem, oraz rsa_key.der i rsa_key.pub.der w starszych instalacjach: klucz podpisujący tokeny logowania.
  • icon_cache/: pobrane ikony stron internetowych. Jest to jedyny katalog, który można pominąć, ponieważ Vaultwarden pobierze je ponownie na żądanie.

Czy baza danych Vaultwarden jest bezpieczna? Co faktycznie zawiera plik

Dwa polecenia udzielają odpowiedzi na to pytanie; oba można wykonać natychmiast.

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;"

Pierwsze z nich wyświetla adresy e-mail użytkowników w postaci jawnej. Drugie wyświetla nazwę jednego elementu i wygląda następująco:

2.k9Qw1nQ0y7Yy2Xw==|E1r0J3l5s7d9f1g3h5j7k9==|Lm4nOp6qRs8tUv0wXy2zAb4cDe6fGh8i=

Nazwy elementów, nazwy użytkowników, hasła oraz notatki są szyfrowane przez klienta przed wysłaniem, dlatego serwer przechowuje tekst zaszyfrowany, którego nie potrafi odczytać. Prefiks 2. oznacza typ szyfrowania Bitwarden, po którym następuje wektor inicjalizacji (IV), tekst zaszyfrowany oraz kod uwierzytelniania wiadomości (MAC), każdy w formacie base64 i rozdzielony za pomocą |. Klucz deszyfrujący jest wyprowadzany z głównego hasła konta, które nigdy nie trafia na serwer w użytecznej formie. Ten mechanizm jest identyczny niezależnie od tego, czy uruchomiono Vaultwarden, czy oficjalny serwer, co opisuje porównanie Vaultwarden i samodzielnie hostowanego Bitwarden.

Pozostała część bazy danych nie jest szyfrowana. Adresy e-mail, nazwy kont, podpowiedzi do haseł oraz kody odzyskiwania dwuetapowego są przechowywane jako tekst jawny, obok metadanych takich jak czas utworzenia czy informacja o tym, która organizacja jest właścicielem elementu. Z tego powodu sam plik kopii zapasowej jest poufny. Każdy, kto go posiada, dowiaduje się, kim są użytkownicy, i może przeprowadzić atak na zaszyfrowane dane w trybie offline z prędkością ograniczoną jedynie wydajnością sprzętu. Ten fakt determinuje zasady przechowywania opisane w dalszej części: kopia jest szyfrowana przed opuszczeniem serwera. Token administratora stanowi drugą połowę tego samego problemu, a proces utwardzania samodzielnie hostowanego Vaultwarden obejmuje oba te zagadnienia.

Dlaczego kopiowanie db.sqlite3 podczas działania Vaultwarden nie jest kopią zapasową

Vaultwarden domyślnie uruchamia SQLite w trybie WAL (ENABLE_DB_WAL=true). Zapis trafia najpierw do db.sqlite3-wal, a dopiero operacja checkpoint scala go z db.sqlite3. Skopiowanie samego db.sqlite3 powoduje uzyskanie stanu bazy danych z momentu ostatniego checkpointu, więc hasło zapisane dziesięć minut wcześniej może nie znaleźć się w archiwum, a użytkownik nie otrzyma żadnego ostrzeżenia.

Kopiowanie wszystkich trzech plików za pomocą cp również nie rozwiązuje problemu. Kopie są tworzone w nieco innych momentach, więc zapisany plik WAL może opisywać wersje stron, które nie pasują już do zapisanego pliku głównego. SQLite próbuje wtedy odtworzyć jeden z drugiego, co prowadzi do błędnych wyników. Problem ujawnia się znacznie później:

Error: database disk image is malformed

.backup pozwala uniknąć tego problemu, ponieważ korzysta z interfejsu Online Backup API, który według dokumentacji SQLite jest właściwym sposobem kopiowania bazy danych używanej w czasie rzeczywistym. Narzędzie to odczytuje strony pod blokadą odczytu i restartuje operację, jeśli w międzyczasie proces zapisu zmodyfikuje plik, dzięki czemu na dysku zapisywany jest jeden spójny stan.

Wykonanie kopii bazy danych za pomocą 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;"

Ostatnie polecenie wypisuje ok w osobnej linii. Każdy inny wynik oznacza, że kopia nie nadaje się do użytku; nie należy jej zachowywać, ani usuwać poprzedniej wersji. Cała procedura jest wykonywana na działającym serwerze, dzięki czemu nikt nie zostaje wylogowany, a kontenery nie są restartowane.

Narzędzie sqlite3 nie znajduje się wewnątrz kontenera Vaultwarden. Obraz jest budowany na debian:trixie-slim przy użyciu ca-certificates, curl, libmariadb3, libpq5 oraz openssl, dlatego docker exec vaultwarden sqlite3 ... kończy się błędem:

exec: "sqlite3": executable file not found in $PATH

Należy uruchomić je na hoście, wskazując zamontowaną ścieżkę, co realizują powyższe polecenia. Jeśli dane znajdują się w nazwanym wolumenie, docker volume inspect <name> wyświetli ścieżkę hosta wewnątrz /var/lib/docker/volumes/.

Vaultwarden posiada również własne polecenie tworzenia kopii zapasowych od wersji 1.32.1. Na serwerze:

docker exec -it vaultwarden /vaultwarden backup

Uruchamia ono VACUUM INTO i zapisuje db_YYYYMMDD_HHMMSS.sqlite3 w folderze z danymi. Należy pamiętać o dwóch kwestiach. Kopia trafia obok oryginału na ten sam dysk, więc jest to etap przygotowawczy, a nie pełnoprawna kopia zapasowa. Ponadto dotyczy to tylko SQLite: w przypadku MariaDB lub PostgreSQL polecenie kończy się błędem The database type is not SQLite. Backups only works for SQLite databases.

Pliki, o których zapominają użytkownicy

attachments/ przechowuje tekst zaszyfrowany pod nieprzejrzystymi nazwami. Wiersz bazy danych dla każdego załącznika zawiera jego zaszyfrowaną nazwę pliku oraz materiał klucza, którego klient potrzebuje do odszyfrowania pliku. Załączniki bez bazy danych są nieczytelnym szumem, a baza danych bez załączników dostarcza użytkownikom elementy, których pobieranie kończy się niepowodzeniem. Należy zabezpieczać oba elementy w tym samym przebiegu.

config.json przechowuje wszystko, co zapisano z poziomu strony administratora, a jego wartości mają pierwszeństwo przed pasującymi zmiennymi środowiskowymi. Działa to w obie strony: przywrócenie starego pliku config.json po cichu nadpisuje ustawienia w pliku compose, a sam plik jest wrażliwy, ponieważ może zawierać hasło SMTP oraz token administratora. Token ten należy przechowywać jako ciąg Argon2id PHC (password hashing competition), a nie jako zwykły tekst. docker run --rm -it vaultwarden/server /vaultwarden hash wygeneruje taki ciąg.

rsa_key.pem podpisuje tokeny JSON web tokens (JWT), które utrzymują sesje zalogowanych klientów. Jeśli plik nie istnieje podczas uruchamiania, Vaultwarden generuje nowy klucz, przez co każdy token podpisany starym kluczem przestaje być poprawny i wszyscy klienci zostają wylogowani. Zawartość Vaultwarden przetrwa taką operację, ponieważ jest szyfrowana kluczami pochodzącymi z hasła głównego. Przywrócenie pliku klucza pozwala uniknąć masowego wylogowania.

sends/ przechowuje pliki powiązane z linkami Send. Ich brak powoduje jedynie błędy przy pobieraniu tych konkretnych plików.

Umieszczenie całości w jednym skrypcie

#!/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"

Zapisz plik jako /usr/local/sbin/vw-backup.sh, nadaj mu uprawnienia chmod 700 i uruchom jako root. Linia test wykonuje właściwą pracę: sqlite3 kończy działanie z kodem 0 nawet wtedy, gdy PRAGMA integrity_check zgłasza uszkodzenie, dlatego porównanie wyniku z ok sprawia, że błędna kopia przerywa działanie skryptu. set -euo pipefail następnie zatrzymuje cały proces, zamiast pozwalać tar na utworzenie poprawnego archiwum wokół uszkodzonej bazy danych.

Ostateczne polecenie tar -tzf wyświetla listę faktycznie przechwyconych danych. Przejrzyj ją za pierwszym razem. Należy szukać ./db.sqlite3, ./rsa_key.pem, ./config.json oraz ./attachments/, a także upewnić się, że nie występuje ./db.sqlite3-wal. Uruchamiaj skrypt co noc za pomocą usługi i timera systemd, zamiast używać cron, jeśli wymagasz wyjścia journalctl oraz jednostki zgłaszającej błędy.

Weryfikacja kopii zapasowej poprzez przywrócenie jej do katalogu tymczasowego

Nietestowana kopia zapasowa jest jedynie przypuszczeniem. Przywrócenie danych do katalogu tymczasowego zajmuje minutę i nie wpływa na działające środowisko.

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

Istotne są cztery wyniki. integrity_check wyświetla ok. Liczba użytkowników musi zgadzać się z liczbą znanych kont. Liczba szyfrogramów powinna być zbliżona do wartości z działającego systemu, uzyskanej za pomocą sudo sqlite3 /opt/vaultwarden/data/db.sqlite3 "select count(*) from ciphers;"; dla aktywnego sejfu nigdy nie powinna wynosić zero. Rozmiar katalogu z załącznikami powinien być zbliżony do oczekiwanego (można pominąć ten krok, jeśli nikt nie przesyła załączników). Następnie należy uruchomić sudo rm -rf /tmp/vw-check, ponieważ katalog ten zawiera teraz drugą kopię wszystkich danych.

Podczas przywracania ręcznie skopiowanego folderu danych obowiązuje jedna zasada: przed uruchomieniem serwera należy usunąć db.sqlite3-wal oraz db.sqlite3-shm. W przeciwnym razie SQLite spróbuje odzyskać przywróconą bazę danych przy użyciu dziennika należącego do innej kopii, co doprowadzi do uszkodzenia poprawnie przywróconej bazy. Archiwa utworzone za pomocą powyższego skryptu nigdy nie zawierają tych plików, ponieważ .backup zapisuje jedną kompletną bazę danych.

Przywracanie danych na serwerze

Operacje te wykonuje się na własnym serwerze przy zatrzymanym kontenerze. Vaultwarden nie może zapisywać danych w momencie, gdy zawartość folderu danych ulega zmianie.

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

W chown należy podać użytkownika, na którego uprawnieniach działa kontener. Standardowy obraz działa jako root, więc root:root jest poprawne, chyba że w pliku compose zdefiniowano user: – w takim przypadku należy użyć odpowiedniego uid oraz gid. Folder danych, do którego serwer nie ma uprawnień zapisu, skutkuje stroną logowania odrzucającą każde żądanie, co jest odnotowywane w dziennikach.

Poprawne uruchomienie kończy się linią Rocket:

[INFO] Rocket has launched from http://0.0.0.0:80

Następnie należy zalogować się przez przeglądarkę, otworzyć element i pobrać jeden załącznik. Logowanie działające przy jednoczesnym błędzie pobierania załączników oznacza, że archiwum zawiera bazę danych, ale nie attachments/. Należy zachować data.old.* do momentu pełnej weryfikacji, a następnie usunąć plik. Wycofanie zmian (rollback) przebiega w tych samych trzech krokach, z zamienionymi miejscami katalogami.

Jeśli używane ścieżki różnią się od przedstawionych, przewodnik instalacji Vaultwarden na VPS zawiera plik compose, na którym oparto te polecenia.

Gdzie nie przechowywać kopii zapasowych

  • Nie na tym samym dysku, na którym znajduje się folder z danymi. Awaria wolumenu niszczy obie kopie, podobnie jak jeden rm -rf wykonany w niewłaściwej ścieżce.
  • Nie na tym samym serwerze, nawet jeśli jest to drugi wolumen. Atakujący, który uzyska dostęp do konta root, przejmuje kopie zapasowe w tej samej sesji.
  • Nie w pamięci obiektowej bez szyfrowania, ponieważ archiwum zawiera adresy e-mail, podpowiedzi do haseł, kody odzyskiwania oraz szyfrogramy sejfu, które można atakować offline.
  • Nie tylko w migawkach dostawcy. Przywracają się one szybko, co jest zaletą, ale znajdują się na tym samym koncie co serwer, więc problem z kontem powoduje utratę obu.

Kopia zewnętrzna (offsite) to zastosowanie dla restic, ponieważ repozytorium restic jest szyfrowane na maszynie przed wysłaniem jakichkolwiek danych. Na serwerze:

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

Skieruj restic na katalog archiwum, a nie na folder z aktywnymi danymi, aby przesyłana była spójna kopia, którą już zweryfikowano. Przechowuj hasło do repozytorium w miejscu innym niż serwer, który ono zabezpiecza: utrata hasła sprawia, że migawki stają się nieczytelne, zgodnie z założeniami projektowymi. Jeśli pamięć masowa na to pozwala, nadaj serwerowi poświadczenia z uprawnieniami do zapisu, ale bez uprawnień do usuwania, aby przejęcie kontroli nad maszyną nie pozwoliło na wymazanie jej własnej historii. Konfiguracja kopii zapasowych restic na VPS szczegółowo omawia repozytorium i harmonogram, a restic w porównaniu z BorgBackup pomaga w wyborze, jeśli decyzja jeszcze nie zapadła.

Testowanie przywracania danych według harmonogramu

Należy wyznaczyć jeden dzień w miesiącu na testy. Należy pobrać najnowszy snapshot do katalogu tymczasowego za pomocą restic restore latest --tag vaultwarden --target /tmp/vw-check, wykonać to samo PRAGMA integrity_check, przeprowadzić weryfikację liczby wierszy, a następnie zapisać datę oraz uzyskane wyniki. Kopia zapasowa, która nie była przywracana przez sześć miesięcy, jest kopią o nieznanym stanie. Ustalanie jej stanu podczas awarii jest najgorszym możliwym momentem.

Raz w roku należy przeprowadzić pełną procedurę testową. Należy uruchomić drugi kontener Vaultwarden na wolnym porcie, używając przywróconego katalogu danych, a następnie zalogować się na rzeczywiste konto. Potwierdza to poprawność ścieżki głównego hasła (master password) w pełnym zakresie, czego nie jest w stanie zweryfikować samo liczenie wierszy. Wykonanie restic check --read-data-subset=10% zgodnie z tym samym harmonogramem pozwala zweryfikować, czy przechowywane dane są możliwe do odczytania, a nie tylko widoczne na liście.

FAQ

Czy mogę skopiować plik db.sqlite3 za pomocą cp, gdy Vaultwarden jest uruchomiony?

Nie. Vaultwarden używa SQLite w trybie WAL, więc ostatnie zapisy znajdują się w db.sqlite3-wal i nie zostały jeszcze przeniesione do db.sqlite3. Wykonanie cp samego głównego pliku spowoduje ich cichą utratę, a kopiowanie obu plików osobno może doprowadzić do powstania niespójnej pary, co później objawi się jako Error: database disk image is malformed. Zamiast tego należy użyć sqlite3 /path/db.sqlite3 ".backup '/path/out.sqlite3'". Wykorzystuje ono interfejs Online Backup API bazy SQLite i tworzy jeden spójny plik, podczas gdy serwer nieprzerwanie obsługuje żądania.

Czy muszę zatrzymywać kontener Vaultwarden, aby wykonać kopię zapasową?

Nie, i właśnie na tym polega zaleta .backup. Kopia bazy danych jest bezpieczna na działającym serwerze. Załączniki oraz pliki typu Send są zapisywane w momencie przesyłania przez użytkownika, więc plik dodany między kopiowaniem bazy danych a tar może nie trafić do nocnego archiwum, co w najgorszym przypadku skutkuje utratą jednego załącznika. Jeśli kilkusekundowa przerwa w działaniu nie stanowi problemu, wykonanie docker compose stop przed skryptem i docker compose start po nim eliminuje nawet to ryzyko.

Co się stanie, jeśli przywrócę dane bez plików rsa_key?

Vaultwarden wygeneruje nowy klucz przy starcie. Klucz ten podpisuje tokeny JSON web tokens (JWT), które utrzymują sesje, więc każdy istniejący token przestanie być ważny, wszyscy klienci zostaną wylogowani i będą musieli zalogować się ponownie. Zawartość skarbca pozostaje nienaruszona, ponieważ jest szyfrowana kluczami pochodzącymi z hasła głównego użytkownika, a nie kluczem RSA. Przywróć rsa_key.pem wraz z resztą folderu danych, a nikt nie zauważy procesu przywracania.

Czy archiwum kopii zapasowej jest bezpieczne do przesłania do pamięci obiektowej w obecnej formie?

Nie. Nazwy elementów, hasła i notatki są tekstem zaszyfrowanym, ale adresy e-mail, nazwy kont, podpowiedzi do haseł i kody odzyskiwania dwuetapowego są w bazie danych zapisane otwartym tekstem. Atakujący posiadający kopię bazy może łamać szyfr we własnym tempie. Archiwum należy zaszyfrować przed opuszczeniem maszyny. Repozytorium restic wykonuje to automatycznie, a gpg --symmetric --cipher-algo AES256 vw-20260805-030000.tar.gz tworzy pojedynczy zaszyfrowany plik, który można przekazać do dowolnej pamięci masowej.

Jak wykonać kopię zapasową Vaultwarden na PostgreSQL lub MariaDB?

Kroki dla SQLite nie mają tu zastosowania, a wbudowane polecenie zwróci błąd The database type is not SQLite. Backups only works for SQLite databases. Należy wykonać zrzut bazy danych za pomocą natywnego narzędzia, pg_dump lub mysqldump, zachowując wszystkie pozostałe zasady. Zrzut powinien znaleźć się w jednym archiwum wraz z attachments/, sends/, config.json oraz plikami rsa_key. Wszystkie dane należy pobrać w tej samej sesji, zaszyfrować i przechowywać w lokalizacji innej niż serwer, na którym powstały.