Como fazer backup e restaurar o Vaultwarden na VPS
Use sqlite3 .backup para copiar o Vaultwarden em execução, preserve attachments, config.json e rsa_key e valide a restauração antes de precisar dela.
O que um backup do Vaultwarden deve conter
Um backup do Vaultwarden é uma cópia de toda a pasta de dados, e a base de dados que está dentro dela tem de ser copiada da forma correta. Execute sqlite3 db.sqlite3 ".backup out.sqlite3" em vez de cp, porque uma cópia simples de uma base de dados que está a ser escrita pode produzir um ficheiro que não abre. Depois, mantenha os ficheiros que estão junto dela. Esta é a parte que muitas pessoas esquecem.
Numa instalação Docker, a pasta de dados é o caminho que montou em /data. Pode ser um caminho no host ou um volume nomeado. A diferença entre bind mounts e volumes nomeados determina onde os dados do seu vault estão realmente no disco. Esta é a estrutura dessa pasta.
db.sqlite3: todas as contas, todos os itens do vault, todas as pastas e todas as organizações. Perder este ficheiro significa perder o vault.db.sqlite3-waledb.sqlite3-shm: o write-ahead log (WAL) e o respetivo índice de memória partilhada. As escritas recentes ficam aqui até o SQLite as integrar no ficheiro principal.attachments/: os ficheiros anexados pelos utilizadores aos itens do vault, encriptados, num diretório por item.sends/: os ficheiros associados às ligações do Bitwarden Send.config.json: todas as definições guardadas na página de administração.rsa_key.pem, além dersa_key.derersa_key.pub.dernas instalações mais antigas: a chave que assina os tokens de início de sessão.icon_cache/: ícones de sites transferidos. Este é o único diretório que pode omitir, porque o Vaultwarden volta a transferi-los quando necessário.
A base de dados do Vaultwarden está segura? O que o ficheiro contém realmente
Duas instruções respondem a essa questão, e pode executar ambas agora.
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;"A primeira apresenta os endereços de email dos seus utilizadores em texto simples. A segunda apresenta o nome de um item, com este aspeto:
2.k9Qw1nQ0y7Yy2Xw==|E1r0J3l5s7d9f1g3h5j7k9==|Lm4nOp6qRs8tUv0wXy2zAb4cDe6fGh8i=Os nomes dos itens, nomes de utilizador, palavras-passe e notas são cifrados pelo cliente antes de serem enviados, por isso o servidor armazena texto cifrado que não consegue ler. O prefixo 2. identifica o tipo de cifragem do Bitwarden. Seguem-se um vetor de inicialização (IV), o texto cifrado e um MAC (código de autenticação de mensagem), todos em base64 e separados por |. A chave que o decifra é derivada da palavra-passe principal da conta, que nunca chega ao servidor numa forma utilizável. Esta parte é idêntica tanto no Vaultwarden como no servidor oficial, como explica a comparação entre o Vaultwarden e o Bitwarden autoalojado.
O restante conteúdo da base de dados não é cifrado. Os endereços de email, nomes das contas, sugestões de palavras-passe e códigos de recuperação da autenticação de dois fatores são armazenados em texto simples, juntamente com metadados como as datas de criação e a organização proprietária de um item. Por isso, o próprio ficheiro de backup é um segredo. Quem o obtiver fica a saber quem são os seus utilizadores e pode atacar os blobs cifrados offline à velocidade permitida pelo seu hardware. Este facto determina as regras de armazenamento apresentadas mais abaixo: a cópia é cifrada antes de sair do servidor.
Por que copiar db.sqlite3 enquanto o Vaultwarden está em execução não é um backup
Por padrão, o Vaultwarden executa o SQLite no modo WAL (ENABLE_DB_WAL=true). Uma gravação é feita primeiro em db.sqlite3-wal e só é incorporada a db.sqlite3 durante um checkpoint. Se copiar apenas db.sqlite3, obterá o estado da base de dados no último checkpoint. Assim, uma palavra-passe guardada há dez minutos pode não estar no arquivo, sem qualquer aviso.
Copiar os três ficheiros com cp também não resolve o problema. As cópias são feitas em momentos ligeiramente diferentes. Por isso, o WAL guardado pode descrever versões de páginas que já não correspondem ao ficheiro principal guardado. O SQLite recupera um ficheiro a partir do outro, e o resultado fica incorreto. Só descobrirá o problema muito mais tarde:
Error: database disk image is malformed.backup evita este problema porque utiliza a Online Backup API do SQLite. A documentação do SQLite indica esta API como o método para copiar uma base de dados que pode estar em uso ativo. A API lê as páginas sob um bloqueio de leitura e reinicia a operação se um processo de escrita alterar o ficheiro durante a cópia. Assim, o que fica gravado no disco corresponde a um único momento consistente.
Faça uma cópia da base de dados com 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;"O último comando imprime ok numa linha própria. Qualquer outro resultado significa que a cópia não pode ser utilizada. Não a mantenha nem elimine a cópia anterior. Toda a sequência é executada num servidor ativo. Nenhum utilizador termina a sessão e nenhum contentor é reiniciado.
A ferramenta sqlite3 não está dentro do contentor Vaultwarden. A imagem é baseada em debian:trixie-slim com ca-certificates, curl, libmariadb3, libpq5 e openssl, pelo que docker exec vaultwarden sqlite3 ... falha com:
exec: "sqlite3": executable file not found in $PATHExecute-a no host contra o caminho montado, como fazem os comandos acima. Se os dados estiverem num volume nomeado, docker volume inspect <name> imprime o caminho no host sob /var/lib/docker/volumes/.
O Vaultwarden também inclui o seu próprio comando de cópia de segurança desde a versão 1.32.1. No servidor:
docker exec -it vaultwarden /vaultwarden backupO comando executa VACUUM INTO e grava db_YYYYMMDD_HHMMSS.sqlite3 na pasta de dados. Daqui resultam duas limitações. A cópia fica junto do original no mesmo disco, pelo que esta é uma etapa de preparação e ainda não uma cópia de segurança. Além disso, o comando funciona apenas com SQLite: em MariaDB ou PostgreSQL, termina com The database type is not SQLite. Backups only works for SQLite databases.
Os ficheiros que todos esquecem
attachments/ contém os textos cifrados com nomes opacos. A linha da base de dados de cada anexo contém o nome de ficheiro cifrado e o material criptográfico de que um cliente precisa para desencriptar o ficheiro. Os anexos sem a base de dados são dados ilegíveis, e uma base de dados sem os anexos apresenta aos utilizadores itens cujos downloads falham. Faça ambas as cópias na mesma execução.
config.json contém tudo o que guardou na página de administração, e os respetivos valores têm precedência sobre as variáveis de ambiente correspondentes. Isto pode causar problemas nos dois sentidos: restaurar um config.json antigo substitui silenciosamente as definições do seu ficheiro compose, e o próprio ficheiro é sensível porque pode conter a palavra-passe SMTP e o token de administrador. Armazene esse token como uma string PHC (password hashing competition) Argon2id, em vez de texto simples. docker run --rm -it vaultwarden/server /vaultwarden hash gera uma por si.
rsa_key.pem assina os tokens Web JSON (JWT) que mantêm as sessões dos clientes iniciadas. Se o ficheiro não existir durante o arranque, o Vaultwarden gera uma nova chave. Por isso, todos os tokens assinados com a chave antiga deixam de ser válidos e todos os clientes terminam a sessão. O conteúdo do Vault permanece intacto, porque é cifrado com chaves derivadas da palavra-passe principal. Restaurar o ficheiro de chave evita terminar todas as sessões.
sends/ contém os ficheiros associados às ligações Send. A sua ausência interrompe esses downloads e não afeta mais nada.
Coloque tudo num único script
#!/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"Guarde-o como /usr/local/sbin/vw-backup.sh, torne-o executável com chmod 700 e execute-o como root. A linha test faz trabalho real: sqlite3 termina com o código 0 mesmo quando PRAGMA integrity_check comunica corrupção. Por isso, comparar a saída com ok é o que transforma uma cópia inválida numa falha do script. Em seguida, set -euo pipefail interrompe tudo, em vez de permitir que tar crie um arquivo organizado em torno de uma base de dados corrompida.
O tar -tzf final lista o que foi efetivamente capturado. Leia essa saída na primeira execução. Procure ./db.sqlite3, ./rsa_key.pem, ./config.json e ./attachments/, e confirme a ausência de ./db.sqlite3-wal. Execute-o todas as noites com um serviço e um temporizador systemd em vez de cron, se quiser uma saída journalctl e uma unidade que comunique a falha.
Verifique o backup restaurando-o num diretório temporário
Um backup não testado é apenas uma suposição. Restaurá-lo num diretório temporário demora um minuto e não altera nada em produção.
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/attachmentsQuatro resultados são importantes. integrity_check imprime ok. A contagem de utilizadores corresponde ao número de contas que conhece. A contagem de cifras é próxima do valor em produção mostrado por sudo sqlite3 /opt/vaultwarden/data/db.sqlite3 "select count(*) from ciphers;" e nunca é zero num vault em utilização. O diretório de anexos tem aproximadamente o tamanho esperado. Pode ignorá-lo se ninguém carregar anexos. Depois, execute sudo rm -rf /tmp/vw-check, porque esse diretório contém agora uma segunda cópia de tudo.
Ao restaurar qualquer diretório de dados copiado manualmente, elimine db.sqlite3-wal e db.sqlite3-shm antes de iniciar o servidor. Caso contrário, o SQLite tentará recuperar a base de dados restaurada usando um log pertencente a outra cópia. Isso pode corromper uma base de dados que chegou intacta. Os arquivos produzidos pelo script acima nunca contêm esses ficheiros, porque .backup grava uma base de dados completa.
Restaurar no servidor
Execute estes comandos no seu próprio servidor, com o contentor parado. O Vaultwarden não pode escrever enquanto a pasta de dados é alterada por baixo dele.
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 vaultwardenchown tem de indicar o utilizador com que o contentor é executado. A imagem padrão é executada como root, por isso root:root está correto, exceto se tiver definido user: no seu ficheiro Compose. Nesse caso, use esse uid e gid. Se o servidor não puder escrever na pasta de dados, a página de início de sessão será apresentada, mas todos os pedidos falharão. Os logs registam esse erro.
Um arranque saudável termina com a linha Rocket:
[INFO] Rocket has launched from http://0.0.0.0:80Depois, inicie sessão num navegador, abra um item e descarregue um anexo. Se o início de sessão funcionar, mas os downloads de anexos falharem, isso significa que o arquivo continha a base de dados, mas não attachments/. Mantenha data.old.* até confirmar que tudo funciona e elimine-o depois. Para reverter, execute os mesmos três passos, trocando as pastas entre si.
Se os seus caminhos forem diferentes dos apresentados aqui, o guia de instalação do Vaultwarden numa VPS mostra o ficheiro Compose assumido por estes comandos.
Onde não colocar o backup
- Não coloque no mesmo disco que a pasta de dados. Uma falha no volume elimina as duas cópias, tal como um único
rm -rfno caminho errado. - Não coloque no mesmo servidor, nem mesmo num segundo volume. Um atacante que obtenha acesso a root também obtém acesso aos backups na mesma sessão.
- Não coloque num armazenamento de objetos sem encriptação, porque o arquivo contém endereços de email, pistas de palavras-passe, códigos de recuperação e ciphertext do vault que podem ser atacados offline.
- Não dependa apenas dos snapshots do seu provedor. Eles são rápidos de restaurar, o que é útil, mas ficam na mesma conta que o servidor. Por isso, um problema na conta também os afeta.
Uma cópia externa é o cenário em que o restic se enquadra, porque um repositório restic é encriptado na máquina antes de qualquer upload. No seu servidor:
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 --pruneAponte o restic para o diretório do arquivo e não para a pasta de dados em produção. Assim, o que ele envia é a cópia consistente que já verificou. Guarde a palavra-passe do repositório num local diferente do servidor que ela protege. Se perder essa palavra-passe, os snapshots ficam ilegíveis, por definição. Quando o armazenamento permitir, atribua ao servidor credenciais que possam escrever, mas não apagar. Assim, um comprometimento do servidor não pode eliminar o próprio histórico. Configurar backups do restic num VPS explica detalhadamente o repositório e o agendamento, e restic comparado com BorgBackup explica a escolha caso ainda não a tenha feito.
Teste a restauração regularmente
Escolha um dia por mês. Extraia o snapshot mais recente para um diretório temporário com restic restore latest --tag vaultwarden --target /tmp/vw-check, execute o mesmo PRAGMA integrity_check, execute as mesmas contagens de linhas e registe a data e as contagens. Um backup que não foi restaurado durante seis meses tem um estado desconhecido. O seu estado só será descoberto durante uma indisponibilidade, que é o pior momento para o descobrir.
Uma vez por ano, faça o teste completo. Inicie um segundo container do Vaultwarden numa porta disponível, usando a pasta de dados restaurada, e inicie sessão com uma conta real. Isto comprova o funcionamento do percurso da palavra-passe mestra de ponta a ponta, algo que nenhuma contagem de linhas consegue fazer. restic check --read-data-subset=10%, executado com a mesma periodicidade, confirma que os dados armazenados são legíveis, em vez de apenas estarem listados.
FAQ
Posso copiar db.sqlite3 com cp enquanto o Vaultwarden está em execução?
Não. O Vaultwarden executa o SQLite no modo WAL, por isso as gravações recentes ficam em db.sqlite3-wal e ainda não estão em db.sqlite3. Uma cp apenas do ficheiro principal perde-as silenciosamente, e copiar os dois ficheiros separadamente pode deixar um par incompatível que só se manifesta mais tarde como Error: database disk image is malformed. Utilize sqlite3 /path/db.sqlite3 ".backup '/path/out.sqlite3'". Este comando utiliza a SQLite Online Backup API e produz um ficheiro consistente enquanto o servidor continua a atender pedidos.
Tenho de parar o contentor do Vaultwarden para fazer uma cópia de segurança?
Não, e esse é precisamente o objetivo de .backup. A cópia da base de dados é segura num servidor em execução. Os anexos e os ficheiros Send são gravados quando um utilizador carrega um ficheiro, por isso um ficheiro adicionado entre a cópia da base de dados e o tar pode ficar de fora do arquivo dessa noite. No pior caso, perde-se um anexo. Se alguns segundos de indisponibilidade não forem um problema, docker compose stop antes do script e docker compose start depois dele eliminam também essa possibilidade.
O que acontece se eu restaurar sem os ficheiros rsa_key?
O Vaultwarden gera uma nova chave no arranque. Essa chave assina os JSON web tokens (JWT) que mantêm as sessões ativas, por isso todos os tokens existentes deixam de ser válidos e todos os clientes terminam a sessão e têm de iniciar sessão novamente. O conteúdo do cofre não é afetado, porque é cifrado com chaves derivadas da palavra-passe principal de cada utilizador, e não com a chave RSA. Restaure rsa_key.pem juntamente com o restante conteúdo da pasta de dados para que ninguém perceba que a restauração ocorreu.
O arquivo de cópia de segurança é seguro para carregar para um armazenamento de objetos tal como está?
Não. Os nomes dos itens, as palavras-passe e as notas estão cifrados, mas os endereços de email, os nomes das contas, as sugestões de palavras-passe e os códigos de recuperação de autenticação de dois fatores estão em texto simples na base de dados. Um atacante offline pode também tentar quebrar a cifra ao seu próprio ritmo. Cifre o arquivo antes de o retirar da máquina. Um repositório restic faz isso por si, e gpg --symmetric --cipher-algo AES256 vw-20260805-030000.tar.gz produz um único ficheiro cifrado que pode entregar a qualquer serviço de armazenamento.
Como faço uma cópia de segurança do Vaultwarden em PostgreSQL ou MariaDB?
Os passos para SQLite não se aplicam, e o comando integrado recusa a operação com The database type is not SQLite. Backups only works for SQLite databases. Faça o dump da base de dados com a ferramenta nativa correspondente, pg_dump ou mysqldump, e mantenha todas as outras regras. O dump deve ficar num único arquivo com attachments/, sends/, config.json e os ficheiros rsa_key, obtidos na mesma execução, cifrado e armazenado noutro local que não seja o servidor que o criou.