Auto-hospedar Vaultwarden, el gestor de contraseñas compatible con Bitwarden
Sigue esto en tu propio servidor. Empieza gratis, sin tarjeta
Los planes hospedados de Bitwarden están bien hasta que prefieres no guardar las contraseñas de tu equipo en el servidor de alguien más, o simplemente quieres una bóveda que responda en tu propio dominio. Vaultwarden es una reimplementación en Rust, hecha desde cero, de la API del servidor Bitwarden, compatible con las extensiones de navegador, apps móviles y el CLI reales de Bitwarden, y lo bastante liviana como para correr cómodamente en el servidor más barato que vende un proveedor de nube. Al final de esta guía la tendrás corriendo detrás de HTTPS con la sincronización en vivo funcionando, un token admin que ya no está en texto plano, los registros cerrados solo a tu equipo, y un respaldo del que de verdad puedes restaurar.
Qué necesitas
- Un servidor con Docker y el plugin de Docker Compose. Un Hetzner CX23 (2 vCPU, 4 GB de RAM, 40 GB NVMe, 5,99 € al mes sin IVA) es más que suficiente para la bóveda de un hogar o un equipo pequeño.
- Un dominio o subdominio que controles, apuntado a la dirección IP del servidor (un registro A de algo como
vault.tudominio.com). - Cualquier cliente compatible con Bitwarden: la extensión de navegador oficial, la app de escritorio, la app móvil, o el CLI funcionan todos sin modificación contra Vaultwarden.
- Acceso SSH al servidor.
Paso 1: generar un token admin hasheado, no uno en texto plano
La página /admin de Vaultwarden, donde administras usuarios, organizaciones y ajustes del servidor, está protegida por un único valor ADMIN_TOKEN. Una cadena en texto plano todavía funciona, pero la documentación del propio proyecto advierte que eso es inseguro, ya que cualquiera que pueda leer tus variables de entorno o tu archivo compose obtiene el token tal cual. La recomendación actual es guardarlo como un hash Argon2id en su lugar, usando el comando de hasheo integrado de Vaultwarden:
docker run --rm -it vaultwarden/server:1.37.4 /vaultwarden hash
Esto pide una contraseña e imprime un hash en formato PHC que se ve como $argon2id$v=19$m=19456,t=2,p=1$<sal>$<hash> (la cadena de arriba es ilustrativa, la tuya tendrá una sal y un hash diferentes). Esa cadena completa es lo que pones como ADMIN_TOKEN; Vaultwarden hashea los intentos entrantes y compara, así que el token real vive en tu cabeza, no en un archivo de configuración que cualquiera puede buscar con grep.
Ponlo en un archivo .env junto a tu docker-compose.yml, que no se sube al control de versiones:
ADMIN_TOKEN='$argon2id$v=19$m=19456,t=2,p=1$c29tZXNhbHR2YWx1ZQ$c29tZWhhc2h2YWx1ZQ'
Un archivo .env no necesita escapar los caracteres $. Si en cambio pegas el hash directamente en el bloque environment: de docker-compose.yml, cada $ tiene que duplicarse a $$ o Compose intenta interpretarlo como sustitución de variable, lo cual es una razón más por la que el archivo .env es el camino más fácil.
Paso 2: escribir el archivo de Docker Compose
services:
vaultwarden:
image: vaultwarden/server:1.37.4
restart: unless-stopped
environment:
DOMAIN: "https://vault.tudominio.com"
SIGNUPS_ALLOWED: "false"
ADMIN_TOKEN: "${ADMIN_TOKEN}"
volumes:
- ./vaultwarden-data:/data
expose:
- "80"
caddy:
image: caddy:2
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile
- caddy-data:/data
- caddy-config:/config
depends_on:
- vaultwarden
volumes:
caddy-data:
caddy-config:
1.37.4 es el tag de versión estable actual en Docker Hub al momento de escribir esto; fija una versión real en lugar de latest para que una actualización sea algo que corres a propósito. DOMAIN tiene que ser la URL HTTPS exacta donde los clientes van a alcanzar el servidor, Vaultwarden la usa para construir enlaces en correos y para validar los orígenes de WebAuthn y passkeys, así que un desajuste aquí es un bug funcional real, no solo cosmético. SIGNUPS_ALLOWED arranca en true por defecto en una instalación nueva de Vaultwarden, lo que significa que cualquiera que encuentre la URL puede crear una cuenta; ponlo en "false" una vez que existan tus propias cuentas, que es el Paso 5 más abajo. El directorio de datos es un bind mount simple en lugar de un volumen con nombre, específicamente para que un respaldo sea solo copiar una carpeta, ve el Paso 6.
Paso 3: apuntar el dominio al servidor e iniciar la pila
Crea un registro A para tu subdominio apuntando a la IP pública del servidor, luego levanta los contenedores:
docker compose up -d
Caddy solicita un certificado a Let’s Encrypt automáticamente la primera vez que ve tráfico para el dominio en tu Caddyfile (Paso 4), lo cual requiere que el registro DNS ya esté resolviendo y que los puertos 80 y 443 sean alcanzables desde internet, no solo desde tu propia máquina.
Paso 4: agregar el proxy inverso Caddy
vault.tudominio.com {
reverse_proxy vaultwarden:80
}
Esa única línea reverse_proxy es también toda la configuración de WebSocket. Vaultwarden solía necesitar un puerto separado (3012) y una bandera WEBSOCKET_ENABLED para las notificaciones de sincronización en vivo que empujan un elemento nuevo o modificado instantáneamente a tus otros clientes abiertos; desde la versión 1.29.0 esas notificaciones corren sobre el mismo puerto que el resto de la app, y la directiva reverse_proxy de Caddy ya reenvía los encabezados Upgrade y Connection que necesita un handshake de WebSocket sin configuración extra. Si estás adaptando esto para nginx en su lugar, ese es el único par de encabezados que tendrías que agregar a mano, que Caddy te da gratis.
Paso 5: crea tu cuenta, luego cierra la puerta
Visita https://vault.tudominio.com, registra la primera cuenta a través del formulario de registro normal, y confirma que puedes iniciar sesión y agregar un elemento de prueba. Una vez que existan todas las cuentas que tu equipo necesita, apaga los registros:
environment:
SIGNUPS_ALLOWED: "false"
Vuelve a correr docker compose up -d para aplicarlo. De ahí en adelante, las cuentas nuevas se crean desde la página /admin usando tu token hasheado en Argon2 del Paso 1, con invitaciones enviadas por correo si configuraste SMTP, o compartiendo un enlace de invitación manualmente si no lo hiciste.
Paso 6: respaldar el directorio de datos
Todo lo que importa, los datos de la bóveda cifrados, los adjuntos, y la configuración del servidor, vive bajo ./vaultwarden-data. Un tarball diario hacia algún lugar fuera del servidor basta para la mayoría de los casos:
tar czf vaultwarden-backup-$(date +%F).tar.gz ./vaultwarden-data
Guarda un puñado de respaldos recientes y copia al menos uno a algún lugar distinto del servidor mismo, un segundo servidor, almacenamiento de objetos, cualquier lugar que sobreviva a que el disco original falle. Restaurar es la misma operación al revés: detén la pila, extrae el tarball sobre ./vaultwarden-data, vuelve a iniciar la pila.
Paso 7: actualizar sin perder datos
docker compose pull vaultwarden
docker compose up -d vaultwarden
Vaultwarden ejecuta automáticamente al arrancar cualquier migración de esquema que necesite contra la base SQLite (o externa configurada) en /data, así que una actualización normal es solo bajar la imagen nueva y recrear ese único contenedor. Lee las notas de la versión a la que saltas antes de un salto de varias versiones, ya que el proyecto ocasionalmente señala un cambio que rompe algo para quienes se auto-hospedan en una versión específica.
El atajo: hazlo con Railyard
Railyard ofrece Vaultwarden como plantilla de aplicación de un clic (vaultwarden en el catálogo de plantillas), construida desde la imagen oficial vaultwarden/server con un volumen /data persistente ya configurado y ADMIN_TOKEN definido como el único secreto que llenas tú mismo. Despliégala directamente: app.railyard.run/deploy?template=vaultwarden.
Dos diferencias respecto al camino manual que vale la pena conocer: la plantilla actualmente sigue el tag flotante latest en lugar de una versión fija, así que donde esta guía te hace elegir y actualizar una versión específica tú mismo, Railyard se mueve con upstream automáticamente. Y los registros están abiertos por defecto aquí también, el mismo paso SIGNUPS_ALLOWED=false del Paso 5 todavía aplica una vez que existan las cuentas de tu equipo, solo que es una variable de entorno en la página Settings de la app en lugar de una línea en docker-compose.yml. Ve Desplegar con un botón para cómo funcionan las plantillas de un clic en general.
Solución de problemas
El HTTPS nunca se emite, Caddy sigue reintentando. Casi siempre es un problema de DNS: confirma que vault.tudominio.com ya resuelve a la IP del servidor desde fuera de tu propia red (no solo desde el servidor mismo), y que nada aguas arriba está bloqueando los puertos 80 o 443 entrantes. Los logs de Caddy (docker compose logs caddy) imprimen el error exacto de ACME si Let’s Encrypt rechazó la solicitud.
La sincronización en vivo no actualiza otros dispositivos, pero la bóveda misma funciona bien. Revisa que nada entre el cliente y Caddy esté quitando los encabezados de actualización de WebSocket, un CDN o una capa de proxy adicional delante de Caddy es el culpable usual, ya que Caddy mismo maneja esto correctamente de fábrica como se cubrió en el Paso 4.
docker compose up falla con un puerto ya en uso. Algo más en el servidor ya está vinculado al puerto 80 o 443, comúnmente un proxy inverso anterior o un servidor web existente. sudo ss -tlnp | grep ':80\|:443' muestra qué está ocupando el puerto.
La página admin rechaza un token del que estás seguro que es correcto. Confirma que estás pegando la cadena de hash PHC completa incluyendo el prefijo $argon2id$ como ADMIN_TOKEN, no solo la contraseña que hasheaste. Si editaste el valor directamente dentro de docker-compose.yml en lugar de un archivo .env, revisa que cada $ esté duplicado a $$, un solo $ sin escapar es tragado por la sustitución de variables de Compose antes de que Vaultwarden siquiera lo vea.
Las escrituras a /data fallan con un error de permisos. Esto aparece más seguido después de restaurar un respaldo como root mientras el contenedor corre con un usuario distinto; haz chown -R al directorio para que coincida con el UID que muestran los logs del contenedor, y luego reinicia.
Preguntas frecuentes
¿Vaultwarden es realmente compatible con las apps reales de Bitwarden? Sí, implementa la misma API que hablan las apps oficiales y las extensiones de navegador, así que las apuntas a la URL de tu propio servidor en lugar de bitwarden.com y nada más cambia del lado del cliente.
¿Puedo usar otra base de datos distinta del archivo SQLite por defecto? Vaultwarden también soporta MySQL y PostgreSQL, mediante una variable de entorno DATABASE_URL, lo cual vale la pena una vez que más de un puñado de personas dependen de la bóveda y quieres que los respaldos los maneje tu herramienta de base de datos existente en lugar de un tarball.
¿Necesito SMTP configurado para invitar gente? No, puedes compartir un enlace de invitación manualmente desde la página /admin sin configurar nunca correo saliente, aunque las invitaciones reales por correo son más amigables para cualquiera que no esté ya cómodo pegando un enlace crudo en un navegador.
¿Qué pasa si pierdo el servidor sin respaldo? Cada elemento de la bóveda desaparece, no hay forma de restablecer contraseña hacia datos que nunca se respaldaron en otro lugar, que es toda la razón por la que el Paso 6 no es opcional en la práctica aunque Docker funcione contento sin él.
¿Es seguro para toda una empresa, o solo para uso personal? Las organizaciones, colecciones compartidas y permisos de grupo están todos soportados, igual que en Bitwarden hospedado; lo que escala menos bien a mano es todo lo del Paso 6 y el Paso 7, exactamente la parte que una opción gestionada te quita de encima.
Una vez corriendo, una bóveda auto-hospedada mayormente desaparece en segundo plano, el trabajo continuo real es el hábito de respaldo y actualización de los Pasos 6 y 7, no la configuración inicial.
Por qué eligen Railyard para esto
- Tu propia cuenta en la nube. Despliega en DigitalOcean, Hetzner, Vultr, AWS, OVHcloud, o cualquier servidor Linux por SSH, facturado directamente por tu proveedor, sin márgenes adicionales.
- Un precio fijo por equipo. El plan Free es gratis para siempre: un servidor más un servidor de base de datos, tres apps, sin tarjeta. Pro cuesta $19 al mes por hasta cinco servidores y apps ilimitadas. Team cuesta $49 al mes con servidores ilimitados. Sin cargos por app, por despliegue, por asiento ni por gigabyte.
- Plantillas de un clic. Despliega apps de código abierto, como bases de datos, analítica y herramientas de desarrollo, en tu propio servidor con un clic.
- Bases de datos administradas. Postgres, MySQL y Redis, con copias de seguridad programadas y verificadas por restauración, y recuperación a un punto en el tiempo.
- Despliegue con git push. Despliega desde GitHub, GitLab o la CLI, con HTTPS automático y lanzamientos sin tiempo de inactividad.