Como instalar Vaultwarden com Docker em VPS
Guia para rodar Vaultwarden em VPS com Docker. Aprenda a configurar HTTPS, desativar registro público e realizar backups seguros do volume de dados.
O que você está construindo
Um gerenciador de senhas sob seu controle total: Vaultwarden rodando em um único container pequeno atrás de um reverse proxy que termina o HTTPS, com os apps oficiais do Bitwarden no seu celular, laptop e navegador apontados para ele. O Vaultwarden reimplementa a API do servidor Bitwarden em Rust e utiliza o mesmo protocolo do bitwarden.com, portanto todos os clientes oficiais funcionam sem alterações — mas ele consome cerca de 100 MB de RAM, ao contrário da stack oficial de múltiplos containers.
A instalação consiste em uma dúzia de linhas de Compose. Os três pontos que realmente importam — e que costumam falhar — são estes: o TLS deve estar configurado antes de você carregar o web vault, o registro público deve ser desativado assim que sua conta for criada, e o volume de dados deve ser feito backup e testado em uma restauração, pois esse diretório contém todas as suas senhas.
Pré-requisitos e problemas comuns
- Uma VPS com Docker Engine e o plugin Compose, em um servidor Ubuntu 24.04 KVM recém-instalado com acesso root ou sudo. 512 MB de RAM é suficiente; 1 GB é o ideal. Este é um dos serviços mais leves que você pode rodar — ele está no topo da lista de serviços que valem a pena hospedar por conta própria.
- Um domínio com um registro A (e AAAA se você usar IPv6) apontando
vault.example.compara a VPS. O certificado TLS é emitido para este nome exato, portanto o DNS deve estar configurado antes de começar. - Portas 80 e 443 abertas para a internet, direcionadas ao seu reverse proxy — nunca diretamente ao Vaultwarden. A porta 80 é usada apenas para o desafio de certificado ACME e para o redirecionamento de HTTP para HTTPS.
- O principal problema inicial: os clientes do Bitwarden não aceitam conexões com servidores que não utilizam HTTPS. Não é possível "testar via HTTP primeiro" — esse método não funciona por um motivo específico detalhado a seguir.
Por que Vaultwarden, e não o stack oficial do Bitwarden
Mesmos clientes, uma fração do peso. O Bitwarden oficial self-hosted é entregue como um conjunto de containers (MSSQL, Nginx, Identity, Api, Admin e outros) e exige aproximadamente 2 GB de RAM. O Vaultwarden é um binário único que armazena tudo em um banco de dados SQLite por padrão e consome apenas alguns dezenas de megabytes em idle. Para uma pessoa, uma família ou uma pequena equipe, é a escolha óbvia; como ele implementa a API do Bitwarden fielmente, seus dados permanecem portáteis entre ele e o bitwarden.com.
O que você perde é a maior parte da superfície enterprise: não há provisionamento SCIM (embora o OpenID Connect SSO experimental tenha chegado na versão 1.35.0), e você é o operador, portanto, aplicar patches, configurar HTTPS e realizar backups é sua responsabilidade. Este guia cobre essas três tarefas.
Por que o HTTPS não é opcional
O Bitwarden web vault e as extensões de browser derivam suas chaves de criptografia no browser usando a Web Crypto API (window.crypto.subtle). Browsers só expõem crypto.subtle em um secure context — HTTPS, ou o caso especial de http://localhost. Sobre http://vault.example.com puro, o recurso é undefined, então assim que o app tenta derivar uma chave, ele falha e o console exibe:
Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'importKey')A página trava ou exibe um erro genérico de criptografia, e o login não funciona. Os clientes desktop, mobile e browser executam sua própria verificação contra uma URL self-hosted; contra um endpoint http (ou inacessível), eles recusam com:
This is not a recognized Bitwarden server. You may need to check with your provider or update your server.Ambos possuem a mesma causa: falta de HTTPS válido. Portanto, configure o TLS primeiro e nunca abra o vault via http, nem mesmo uma única vez para uma visualização rápida.
Passo 1 — DNS e o proxy reverso (TLS primeiro)
Aponte o registro para o seu VPS e confirme se ele resolve para o endereço correto:
dig +short vault.example.comA linha impressa deve ser o IP do seu VPS. Se estiver em branco ou incorreto, corrija o DNS e aguarde o TTL — a emissão do certificado falha se o nome não resolver.
Para o front end HTTPS, este guia utiliza o Traefik, que emite e renova certificados Let's Encrypt automaticamente e integra-se diretamente ao Compose. Se você ainda não o utiliza, siga primeiro o setup do proxy reverso Traefik e TLS automático; ele cria uma rede Docker externa (proxy abaixo) e um resolver ACME (letsencrypt) ao qual o serviço Vaultwarden se conecta. O nginx padrão com um certificado manual funciona de forma idêntica para o Vaultwarden.
Prefere nginx e Certbot em vez de Traefik? Coloque o Vaultwarden em 127.0.0.1:8080 (adicione ports: ["127.0.0.1:8080:80"] ao serviço e remova as labels do Traefik), então emita um certificado e faça o proxy para ele. A parte do certificado é abordada em emissão de certificados Let's Encrypt com Certbot e nginx. O detalhe crítico adicional é o upgrade do WebSocket no caminho de notificações:
server {
listen 443 ssl;
server_name vault.example.com;
client_max_body_size 525M;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}Observe a linha X-Real-IP — é o que permite ao Fail2ban identificar o atacante real em vez de 127.0.0.1. Todo o restante deste guia é idêntico, independentemente de o Traefik ou o nginx estar à frente.
Passo 2 — o arquivo Compose
Crie o diretório do projeto primeiro. Este guia utiliza /opt/vaultwarden, o que torna o nome do projeto Compose — e, consequentemente, o volume de dados, vaultwarden_vw-data — previsível; as etapas de Fail2ban e backup abaixo dependem desse nome exato.
sudo mkdir -p /opt/vaultwarden
cd /opt/vaultwardenCrie um .env para o segredo do admin e o arquivo Compose nesse diretório.
# .env
ADMIN_TOKEN=paste-a-strong-token-hereGere esse token com openssl rand -base64 48 e cole-o no arquivo. (Uma forma de hash mais forte será abordada a seguir; uma string aleatória longa é suficiente para começar.)
# docker-compose.yml
services:
vaultwarden:
image: vaultwarden/server:latest
container_name: vaultwarden
restart: unless-stopped
environment:
DOMAIN: "https://vault.example.com"
SIGNUPS_ALLOWED: "true" # closed in Step 4, keep true just to register
ADMIN_TOKEN: "${ADMIN_TOKEN}"
IP_HEADER: "X-Forwarded-For" # X-Real-IP if your proxy sends that instead
LOG_FILE: "/data/vaultwarden.log"
LOG_LEVEL: "warn"
volumes:
- vw-data:/data
networks:
- proxy
labels:
- "traefik.enable=true"
- "traefik.http.routers.vw.rule=Host(`vault.example.com`)"
- "traefik.http.routers.vw.entrypoints=websecure"
- "traefik.http.routers.vw.tls.certresolver=letsencrypt"
- "traefik.http.services.vw.loadbalancer.server.port=80"
volumes:
vw-data:
networks:
proxy:
external: trueDois detalhes neste arquivo definem todo o design. Não há mapeamento de ports:, portanto o Vaultwarden é acessível apenas via Traefik e seu TLS — publicar a porta no host é como usuários servem o cofre via http por acidente. E DOMAIN deve ser a URL HTTPS pública completa: ela é integrada aos links de anexos, WebAuthn 2FA e ao endpoint de notificações; um valor incorreto ou via http quebra essas funções mesmo que o site carregue. A tag latest é uma exceção deliberada à regra usual de nunca-latest — o Vaultwarden distribui suas versões estáveis como uma única imagem rolling, com :testing como o canal separado de pre-release — portanto, atualize propositalmente e leia as notas de lançamento antes de dar o pull.
Inicie o serviço e acompanhe o log:
docker compose up -d
docker compose logs -f vaultwardenUm início correto termina com uma linha como Rocket has launched from http://0.0.0.0:80. Dê alguns segundos para o Traefik buscar o certificado e, em seguida, carregue https://vault.example.com — você deve acessar o web vault do Bitwarden com um cadeado válido e sem avisos de certificado.
Passo 3 — um ADMIN_TOKEN forte e a armadilha $$
ADMIN_TOKEN protege o /admin, o painel que pode ler todos os usuários e configurações da sua instância; trate-o como uma senha de root. Duas formas funcionam.
A forma simples é a string aleatória que você já gerou com openssl rand -base64 48. Como o base64 nunca contém um $, ele é inserido diretamente no .env sem necessidade de escaping.
A forma segura é um hash Argon2 PHC, assim o token em texto plano nunca é armazenado no disco. Gere um usando a mesma imagem:
docker run --rm -it vaultwarden/server /vaultwarden hash --preset owaspO comando solicita a entrada duas vezes e imprime uma string começando com $argon2id$v=19$.... Aqui está a armadilha que faz usuários perderem uma hora: o Docker Compose trata $ como interpolação de variável, portanto você deve duplicar cada $ para $$ ao colar o hash no arquivo Compose. Coloque-o diretamente sob environment:, não via .env, e não o coloque entre aspas:
environment:
ADMIN_TOKEN: $$argon2id$$v=19$$m=19456,t=2,p=1$$c29tZXNhbHQ$$RdescudvJCsgt3ub+b+dWRWJTmaaJObGSe você deixar apenas um $, o Compose emitirá um aviso The "argon2id" variable is not set e deixará o token vazio, e o /admin rejeitará sua senha correta. Execute docker compose up -d e guarde o texto plano digitado no prompt em seu próprio gerenciador de senhas.
Passo 4 — registre sua conta e depois feche o acesso
Com o SIGNUPS_ALLOWED: "true", abra o https://vault.example.com, clique em Create account e registre-se com seu e-mail e uma senha mestra forte. Esta senha mestra nunca pode ser recuperada — não existe reset — portanto, armazene-a em um local seguro primeiro.
Agora feche o acesso. Edite o arquivo Compose para desativar novos cadastros:
SIGNUPS_ALLOWED: "false"Reaplique com docker compose up -d. Isso não é um hardening obrigatório e pode ser adiado. Se mantido aberto, qualquer pessoa que encontrar a URL — e crawlers encontram — poderá criar uma conta no seu servidor. Eles não podem ler o seu vault, mas consomem recursos e transformam sua instância privada em um serviço público. O sinal de que você deixou o acesso aberto: o /admin lista contas que você nunca criou.
Para adicionar familiares ou membros da equipe posteriormente sem reabrir o cadastro público, use o botão Invite User no /admin; este caminho exige o SMTP configurado para que o convidado receba o link.
Passo 5 — acessando /admin
Acesse https://vault.example.com/admin e insira o token de admin em texto puro (a string aleatória, ou a senha que você transformou em hash — não o hash em si). Lá você pode listar usuários, ajustar configurações, enviar um e-mail de teste e tirar um snapshot do banco de dados.
Se a página retornar 404 Not Found, ADMIN_TOKEN está vazio ou não definido, o que desativa o painel completamente — uma escolha válida se você nunca precisar dele. Se a página carregar mas rejeitar seu token, veja o erro de escape de $$ na lista de falhas abaixo. Esqueceu o token? Não há prompt de recuperação; edite o .env ou o Compose file, defina um novo e docker compose up -d.
Passo 6 — conectar os clientes Bitwarden
Todo cliente oficial pode apontar para um servidor self-hosted. Instale o cliente Bitwarden para desktop, mobile ou browser pelas lojas oficiais — você não precisa de uma build especial do Vaultwarden.
Antes de fazer o login, abra o ícone de configurações na tela de login (identificado como Self-hosted ou Region → Self-hosted), defina a Server URL como https://vault.example.com e salve. Em seguida, faça o login com o e-mail e a master password cadastrados; o cliente deve conectar imediatamente e oferecer o preenchimento e salvamento de credenciais.
Se um cliente exibir This is not a recognized Bitwarden server. You may need to check with your provider or update your server., a URL está incorreta, utiliza http ou o certificado não é confiável — verifique primeiro se https://vault.example.com carrega corretamente em um browser. Atualizações lentas em outros dispositivos ocorrem via WebSocket push, conforme detalhado abaixo.
Passo 7 — uma jail do Fail2ban para o endpoint de login
O Vaultwarden registra cada falha de login no arquivo definido por LOG_FILE — exatamente o que um guardião contra brute-force precisa. Se você ainda não está executando o Fail2ban, a instalação e os conceitos básicos estão no guia de hardening do Fail2ban para SSH; aqui adicionaremos uma jail para o vault.
Primeiro, localize onde o volume nomeado reside no host, para que o Fail2ban consiga ler o log:
docker volume inspect vaultwarden_vw-data --format '{{ .Mountpoint }}'Isso imprimirá algo como /var/lib/docker/volumes/vaultwarden_vw-data/_data; o log é vaultwarden.log dentro dele. Crie o filtro:
# /etc/fail2ban/filter.d/vaultwarden.conf
[Definition]
failregex = ^.*Username or password is incorrect\. Try again\. IP: <ADDR>\. Username:.*$
ignoreregex =E a jail:
# /etc/fail2ban/jail.d/vaultwarden.local
[vaultwarden]
enabled = true
filter = vaultwarden
logpath = /var/lib/docker/volumes/vaultwarden_vw-data/_data/vaultwarden.log
banaction = iptables-allports
chain = DOCKER-USER
maxretry = 5
findtime = 600
bantime = 3600Recarregue com sudo systemctl restart fail2ban e confirme com sudo fail2ban-client status vaultwarden.
Três detalhes do Docker determinam se isso protegerá algo. Primeiro, se o log mostrar IP: 127.0.0.1 ou o endereço do seu proxy em cada tentativa falha, o Vaultwarden banirá o proxy — defina IP_HEADER para o header que seu proxy realmente envia (X-Forwarded-For para Traefik, X-Real-IP para o bloco nginx acima, CF-Connecting-IP atrás do Cloudflare). Segundo, a chain correta do iptables depende do seu proxy: com o Traefik rodando como um container com portas publicadas, o tráfego atravessa o caminho FORWARD do Docker, portanto o banimento deve estar em DOCKER-USER como mostrado acima; mas se você escolheu a opção nginx-host do Passo 1, as conexões terminam no nginx na chain INPUT do host e um banimento DOCKER-USER nunca as verá — nesse caso, delete a linha chain = DOCKER-USER para que o Fail2ban use a chain padrão INPUT. Terceiro, use banaction = iptables-allports em vez do padrão baseado em porta — esta jail não define porta, e um banimento de todas as portas em DOCKER-USER bloqueia o infrator de todos os serviços publicados na máquina de forma limpa.
Passo 8 — faça o backup do vault e depois realize o restore
O volume vw-data é o seu gerenciador de senhas. Ele contém o db.sqlite3 (todas as entradas), os diretórios attachments/ e sends/, os arquivos rsa_key.* que assinam as sessões de login e o config.json do painel de administração. Um backup que ignore qualquer um desses itens falhará quando você precisar.
Copiar o db.sqlite3 enquanto o Vaultwarden está escrevendo pode capturar um arquivo corrompido ou incompleto. Por isso, tire um snapshot com o serviço parado — o downtime é de apenas alguns segundos:
#!/usr/bin/env bash
set -euo pipefail
STAMP=$(date +%F)
DEST=/root/vw-backups
VOL=$(docker volume inspect vaultwarden_vw-data --format '{{ .Mountpoint }}')
mkdir -p "$DEST"
docker compose -f /opt/vaultwarden/docker-compose.yml stop vaultwarden
tar czf "$DEST/vw-$STAMP.tgz" -C "$VOL" .
docker compose -f /opt/vaultwarden/docker-compose.yml start vaultwardenExecute via cron diariamente e copie o .tgz para fora da máquina — um backup que reside apenas no servidor que você está protegendo não é um backup. A forma recomendada de transferi-lo é um backup restic diário para outro servidor ou object storage, que criptografa o arquivo e faz a deduplicação de snapshots repetidos. O botão Backup Database no painel de administração é um snapshot rápido apenas do arquivo SQLite, mas ele omite anexos e chaves.
Agora, o procedimento que diferencia um backup real de um backup incerto — realize o restore uma vez para provar que funciona:
mkdir -p /tmp/vw-restore
tar xzf /root/vw-backups/vw-2026-07-15.tgz -C /tmp/vw-restore
docker run --rm -p 127.0.0.1:8888:80 -v /tmp/vw-restore:/data vaultwarden/serverA partir do seu laptop, faça um túnel para o servidor usando ssh -L 8888:127.0.0.1:8888 you@your-vps e abra o http://localhost:8888. Como o localhost é um contexto seguro, o crypto.subtle fica disponível e o vault descriptografa via http simples aqui — o único local permitido. Faça login com sua master password e confirme se as entradas estão presentes: se estiverem, seu banco de dados, chaves RSA e master password completaram o ciclo de ida e volta, e você poderá reconstruir o ambiente em um novo VPS em minutos. Pare o container com Ctrl-C e delete o /tmp/vw-restore.
Modos de falha e as strings que você verá
Cannot read properties of undefined (reading 'importKey') no console do browser. O vault foi carregado via http, então crypto.subtle está undefined; acesse apenas via https:// e adicione o redirect de HTTP para HTTPS no proxy.
This is not a recognized Bitwarden server... em um client. A Server URL está como http, foi digitada incorretamente ou o certificado não é confiável; confirme se https://vault.example.com exibe um cadeado válido e reinsira a URL nas configurações self-hosted do client.
/admin rejeita a senha correta. O hash Argon2 perdeu o escaping — cada $ deve ser $$ no Compose — ou você inseriu o hash em vez do texto puro que ele representa.
Sincronização entre dispositivos lenta; o console mostra WebSocket connection to 'wss://vault.example.com/notifications/hub' failed. O proxy não está encaminhando os headers Upgrade/Connection; o Traefik faz isso automaticamente, o nginx precisa das duas linhas upgrade do Passo 1. O vault continua funcionando, mas a sincronização ocorre apenas ao abrir. A antiga porta dedicada 3012 foi removida na v1.31.0, portanto não é necessária uma rota WebSocket separada.
Fail2ban reporta um banimento, mas o atacante continua conectando. Ele está banindo 127.0.0.1 porque IP_HEADER está incorreto, ou o banimento está na chain errada do iptables — configure chain = DOCKER-USER e banaction = iptables-allports.
Upgrades
Faça o pull da nova imagem e recrie o container; o volume nomeado e todos os seus dados persistem:
docker compose pull
docker compose up -dO Vaultwarden possui lançamentos frequentes. Acompanhe as notas de lançamento do projeto em vez de fixar uma versão de patch, pois alguns lançamentos contêm notas de migração. Faça um backup completo antes de qualquer atualização de versão principal; você pode reverter restaurando o arquivo tar em um novo volume.
FAQ
O Vaultwarden é o mesmo que o Bitwarden?
É um servidor independente e compatível, não o oficial. O Vaultwarden re-implementa a API do servidor Bitwarden em Rust. Por isso, os clientes oficiais de desktop, mobile, browser e CLI funcionam com ele consumindo uma fração dos recursos da stack oficial. O formato do vault é o mesmo, permitindo a migração em ambos os sentidos via exportação e importação.
Eu realmente preciso de HTTPS ou posso rodar via http na minha LAN?
Você precisa de HTTPS para qualquer coisa que não seja um teste de localhost. O web vault e as extensões do Bitwarden utilizam a Web Crypto API do browser, que só está disponível em contextos seguros. Em http puro, o cliente retorna Cannot read properties of undefined e não realiza o login. O único endereço http que funciona é http://localhost, por isso o teste de restore no Passo 8 utiliza um túnel SSH.
Como impeço que estranhos se registrem no meu servidor?
Configure SIGNUPS_ALLOWED: "false" no arquivo Compose e execute docker compose up -d logo após criar sua própria conta. A partir de então, adicione novas pessoas pelo botão Invite User em /admin. Isso exige configuração de SMTP para que recebam o link de convite. Verifique a lista de usuários admin periodicamente para confirmar se não surgiram contas inesperadas.
Como eu faço o backup do meu vault do Vaultwarden?
Pare o container brevemente e arquive todo o volume vw-data — db.sqlite3, attachments/, sends/, config.json e os arquivos rsa_key.* — então copie o arquivo para fora do servidor, idealmente via cron diário. Copiar o arquivo SQLite com o servidor em execução gera risco de snapshot corrompido; faça o backup com o serviço parado. O mais importante: restaure o backup uma vez em um container temporário e faça o login para garantir que o backup é válido antes de depender dele.
É realmente seguro hospedar minhas senhas por conta própria?
Sim, desde que você siga os três pontos abordados neste guia: HTTPS real, registros fechados com um token de admin forte e backups testados. Seu vault é criptografado no lado do cliente com sua master password; portanto, o servidor nunca vê suas senhas em texto claro — um db.sqlite3 roubado é inútil sem ela. A contrapartida é que a aplicação de patches e os backups passam a ser sua responsabilidade, por isso o Fail2ban e o ritual de restore não são opcionais aqui.