SSD Nodes Learn 🎉 VPS desde $4.99/mes
Guías Matt ConnorPor Matt Connor

¿Es seguro Vaultwarden? Cómo reforzar tu instalación

Vaultwarden cifra cada elemento en el cliente, pero el token de admin y las copias de seguridad siguen expuestos. Revisa puertos, secretos y permisos.

¿Es seguro Vaultwarden? La respuesta corta

Vaultwarden es seguro en el lugar más importante: cada elemento de la bóveda se cifra en su dispositivo antes de llegar al servidor. El servidor almacena datos binarios que no puede leer. Aunque alguien copie toda la base de datos, seguirá necesitando una contraseña maestra para obtener información útil.

Esta respuesta depende de muchos factores, y los puntos que pueden fallar son los que usted configura. Un panel de administración protegido por un token fácil de adivinar. Un puerto de contenedor publicado en todo Internet. Un config.json en texto plano. Un archivo tar de copia de seguridad almacenado en el directorio personal del mismo servidor. Ninguno de estos problemas está relacionado con la criptografía. Todos pueden provocar que se vacíen las bóvedas autohospedadas.

Todo lo que sigue presupone que la instalación funciona. Si todavía no tiene una, configúrela primero con la guía de instalación de Vaultwarden para un VPS y después siga esta lista en orden.

Qué almacena realmente el servidor

Vaultwarden implementa el modelo de datos de Bitwarden. El nombre, el nombre de usuario, la contraseña, las notas y las URI de un elemento de la bóveda se cifran con una clave derivada de la contraseña maestra, en el cliente, antes de enviar cualquier solicitud. El contenido de los archivos adjuntos se cifra de la misma forma. El servidor recibe datos opacos asociados a un UUID (identificador único universal).

Algunos datos no son texto cifrado. Debe saber exactamente cuáles son:

  • La dirección de correo electrónico de la cuenta, en texto plano.
  • La configuración y la sal de la KDF (función de derivación de claves), porque el cliente las necesita para reconstruir la clave en el siguiente inicio de sesión.
  • Un hash del hash de la contraseña maestra que envía el cliente, almacenado en el servidor y utilizado para autenticar el inicio de sesión.
  • Metadatos: pertenencia a organizaciones, nombres de dispositivos y horas del último inicio de sesión.
  • El secreto del método de autenticación de dos factores que protege el inicio de sesión en Vaultwarden. Se almacena sin cifrar en la tabla twofactor, porque el servidor debe calcular el código esperado para compararlo con el suyo. No es lo mismo que un secreto TOTP (contraseña de un solo uso basada en el tiempo) que almacena dentro de un elemento de la bóveda. Ese secreto se cifra como cualquier otro campo.

La carpeta de datos es pequeña. En una instalación de Docker, corresponde a la ruta que montó en /data.

sudo ls -l /vw-data/

db.sqlite3 contiene casi todo el estado. attachments/ contiene los archivos subidos, uno por UUID, y es la única clase importante de datos que no se almacena en tablas de la base de datos. sends/ contiene los archivos adjuntos de Send y está destinado a ser temporal. icon_cache/ contiene datos prescindibles. rsa_key.pem y sus archivos asociados firman los JWT (tokens web JSON) de los usuarios con sesiones iniciadas. Por tanto, una copia de esa clave privada se puede utilizar para falsificar una sesión de inicio de sesión en la bóveda. config.json sólo existe después de habilitar la página de administración. El proyecto lo deja claro: contiene el token de administración y las credenciales SMTP en texto plano.

Por tanto, el modelo de amenazas práctico se centra en el acceso al sistema de archivos, no en la criptografía de red. El acceso de lectura a ese único directorio expone las direcciones de correo electrónico de todos los usuarios, sus secretos de autenticación 2FA, una clave capaz de falsificar sesiones y una copia sin conexión de cada bóveda, que se puede atacar sin límite de tiempo. Cada paso siguiente sirve para impedir el acceso de terceros a ese directorio.

Corrija primero el token de administración

/admin es un panel de control completo: lista de usuarios, invitaciones, eliminación y todos los ajustes de ejecución. Está protegido por un único secreto compartido y nada más. No hay nombre de usuario. No hay autenticación de dos factores por usuario.

Las guías antiguas indican generar ADMIN_TOKEN con openssl rand -base64 48. Funciona y escribe el secreto en texto plano en config.json y en el archivo compose. Vaultwarden también acepta una cadena PHC de Argon2 (password hashing competition), por lo que el valor almacenado es un hash. Genere una contra un contenedor en ejecución:

docker exec -it vaultwarden /vaultwarden hash

O sin tocar el contenedor en ejecución:

docker run --rm -it vaultwarden/server /vaultwarden hash

Solicita la contraseña dos veces y después muestra una línea que empieza por $argon2id$. En una instalación bare metal, ejecute ./vaultwarden hash. Si prefiere usar directamente la CLI de argon2, el proyecto documenta los parámetros mínimos de OWASP:

echo -n 'MySecretPassword' | argon2 "$(openssl rand -base64 32)" -e -id -k 19456 -t 2 -p 1

Ahora viene el problema que hace perder una hora. Una cadena PHC contiene muchos caracteres $, y Docker Compose interpreta $ como interpolación de variables. Si la pega sin escapar en un bloque environment:, el valor que llega al contenedor queda alterado y /admin rechaza un token que sabe que es correcto. Hay dos formas seguras. En docker-compose.yml, duplique cada $:

environment:
  ADMIN_TOKEN: $$argon2id$$v=19$$m=19456,t=2,p=1$$UUZxK1FZMkZoRHFQRlVrTXZvS0E3bHpNQW55c2dBN2NORzdsa0Nxd1JhND0$$cUoId+JBUsJutlG4rfDZayExfjq4TCt48aBc9qsc3UI

En un archivo .env no es necesario escapar los caracteres, pero debe usar comillas simples:

ADMIN_TOKEN='$argon2id$v=19$m=65540,t=3,p=4$MmeKRnGK5RW5mJS7h3TOL89GrpLPXJPAtTK8FTqj9HM$DqsstvoSAETl9YhnsXbf43WeaUwJC6JhViIvuPoig78'

A continuación, limite la tasa de solicitudes del panel y reduzca la duración de su sesión:

ADMIN_RATELIMIT_SECONDS=300
ADMIN_RATELIMIT_MAX_BURST=3
ADMIN_SESSION_LIFETIME=20

Después de tres intentos incorrectos en cinco minutos, el panel deja de responder a ese cliente. La sesión de administración caduca después de 20 minutos de inactividad.

Mejor aún: desactive la página. La mayoría de las instancias sólo la necesitan una vez para configurar SMTP e invitar a los primeros usuarios. Después no vuelven a necesitarla. Para desactivarla, no defina ADMIN_TOKEN ni DISABLE_ADMIN_TOKEN, elimine cualquier clave "admin_token" de config.json y vuelva a crear el contenedor. Eliminar la clave del archivo es importante porque la página de administración escribe allí la configuración y el valor de config.json prevalece sobre el entorno. Si sólo elimina la variable, la página seguirá abierta.

Cierre el registro antes de que alguien encuentre el dominio

SIGNUPS_ALLOWED=false
SIGNUPS_VERIFY=true
SHOW_PASSWORD_HINT=false

SIGNUPS_ALLOWED tiene el valor predeterminado true. Déjelo así y cualquier persona que acceda al dominio podrá crear una cuenta. Sus datos se almacenarán en el mismo db.sqlite3 que los suyos. Establézcalo en false y añada usuarios mediante invitaciones desde la página de administración. Para ello, SMTP debe funcionar. INVITATIONS_ALLOWED también tiene el valor predeterminado true y permite que los propietarios de la organización inviten a otras personas. Esto es adecuado si confía en sus usuarios y debería establecerse en false en una instancia para un solo usuario. Si sólo deben registrarse determinados dominios, SIGNUPS_DOMAINS_WHITELIST=example.com es más restrictivo que el registro abierto, pero mucho menos seguro que las invitaciones.

SHOW_PASSWORD_HINT tiene el valor predeterminado false y debería mantenerse así. Si está habilitado, al introducir una dirección de correo electrónico válida en el formulario de inicio de sesión se muestra la pista de la contraseña maestra de esa cuenta. Esto expone la pista y confirma que la dirección existe.

Si su instancia tuvo el registro abierto durante algún tiempo, abra la página de administración y revise la lista de usuarios antes de dar por hecho que sólo existe su cuenta.

El puerto que no pretendía publicar

La imagen de Docker escucha en el puerto 80 dentro del contenedor. Una instalación directa en el sistema usa ROCKET_PORT=8000 de forma predeterminada. El comando de ejecución documentado lo publica así:

--publish 127.0.0.1:8000:80

El prefijo 127.0.0.1: es el punto clave. Si escribe -p 8000:80, Docker enlaza 0.0.0.0 y lo hace escribiendo reglas DNAT (traducción de direcciones de red de destino) en la tabla nat. Estas reglas se evalúan antes de las cadenas filter que administra ufw. Por eso, ufw status muestra el puerto como denegado, mientras el puerto responde desde Internet. Puede consultar el mecanismo completo en la guía sobre cómo los puertos de Docker evitan ufw.

Compruebe qué está escuchando realmente:

sudo ss -tlnp | grep 8000

El resultado correcto es una sola línea enlazada a 127.0.0.1:8000. Una línea enlazada a 0.0.0.0:8000 significa que vault está expuesto directamente. Corrija la asignación y vuelva a crear el contenedor, porque la vinculación de puertos se fija cuando se crea el contenedor y docker compose restart no la cambiará:

docker compose up -d --force-recreate

En las guías antiguas todavía aparece otro puerto: 3012, el puerto independiente de WebSocket. La compatibilidad con este puerto se eliminó en Vaultwarden 1.31.0, porque el tráfico de notificaciones pasó al puerto HTTP principal. WEBSOCKET_ENABLED y WEBSOCKET_PORT se ignoran desde 1.29.0. La opción actual es ENABLE_WEBSOCKET y su valor predeterminado es true. Si el firewall o el archivo compose todavía abre 3012, ciérrelo.

Termine TLS en un proxy inverso, no en Rocket

Vaultwarden puede gestionar TLS (seguridad de la capa de transporte) directamente mediante Rocket, su framework web, pero el proyecto recomienda no hacerlo en producción. El soporte TLS integrado de Rocket no implementa SNI estricto (indicación del nombre del servidor), por lo que las recomendaciones de hardening también indican que debe acceder a la instancia mediante un nombre de host y nunca mediante una dirección IP sin más. Los rangos de IP públicas se analizan constantemente, y un vault que responde en una dirección IP es un vault que se detecta.

Estas son las partes relevantes de un bloque server de nginx:

client_max_body_size 525M;

location / {
  proxy_pass http://127.0.0.1:8000;
  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_set_header Upgrade $http_upgrade;
  proxy_set_header Connection $connection_upgrade;
}

nginx establece client_max_body_size en 1 MB de forma predeterminada, por lo que, sin esa línea, la carga de un adjunto falla con 413 Request Entity Too Large en el registro de errores de nginx, mientras que Vaultwarden no registra nada. Las cabeceras Upgrade y Connection transportan el handshake de WebSocket hasta /notifications/hub. Si las omite, el vault sigue funcionando, pero los cambios dejan de aparecer en los demás dispositivos hasta que vuelva a cargar la página manualmente.

La configuración de Caddy es más breve y obtiene el certificado por sí mismo:

vw.example.com {
  reverse_proxy 127.0.0.1:8000 {
    header_up X-Real-IP {remote_host}
  }
}

A continuación, indique estos datos a Vaultwarden:

DOMAIN=https://vw.example.com
IP_HEADER=X-Real-IP

IP_HEADER ya tiene X-Real-IP como valor predeterminado, por lo que debe asegurarse de que el proxy establezca realmente esa cabecera. Si no lo hace, cada línea del registro y cada límite de tasa de inicio de sesión verá 127.0.0.1, el propio proxy. Esto provoca que los intentos fallidos de un atacante se contabilicen contra todos los usuarios de la instancia. Establezca DOMAIN también en la URL https real, porque Vaultwarden genera a partir de ella los enlaces de invitación y restablecimiento de contraseñas, y las claves de seguridad WebAuthn están vinculadas a ese origen.

Hay un detalle que suele pasarse por alto: la conexión WebSocket envía el token de sesión en la cadena de consulta, como /notifications/hub?access_token=[JWT]. Ese valor aparece sin cifrar en el registro de acceso del proxy. Redacte el parámetro access_token en el formato del registro o asegúrese de que esos registros no se envíen a ningún lugar que no controle.

Bloquear ataques de fuerza bruta en el endpoint de inicio de sesión

Los límites de tasa están activados de forma predeterminada (LOGIN_RATELIMIT_SECONDS=60, LOGIN_RATELIMIT_MAX_BURST=10). Ralentizan al atacante, pero no lo detienen. fail2ban sí lo hace, aunque Vaultwarden primero debe escribir un archivo de registro y no lo hace de forma predeterminada:

LOG_FILE=/data/vaultwarden.log
LOG_LEVEL=info
EXTENDED_LOGGING=true

Un inicio de sesión fallido genera exactamente una línea. Esta es la cadena que debe coincidir con el filtro:

[2026-08-07 09:14:22][vaultwarden::api::identity][ERROR] Username or password is incorrect. Try again. IP: 203.0.113.10. Username: user@example.com.

Escriba el filtro en /etc/fail2ban/filter.d/vaultwarden.local:

[INCLUDES]
before = common.conf

[Definition]
failregex = ^.*?Username or password is incorrect\. Try again\. IP: <ADDR>\. Username:.*$
ignoreregex =

Y la jail en /etc/fail2ban/jail.d/vaultwarden.local:

[vaultwarden]
enabled = true
port = 80,443
filter = vaultwarden
banaction = %(banaction_allports)s
logpath = /vw-data/vaultwarden.log
maxretry = 3
bantime = 14400
findtime = 14400

Si conservó la página de administración, añada una segunda jail cuyo failregex sea ^.*Invalid admin token\. IP: <ADDR>.*$, porque los fallos de administración se registran con un mensaje diferente y el filtro de inicio de sesión nunca los detectará. Después, compruebe la configuración:

sudo systemctl restart fail2ban
sudo fail2ban-client status vaultwarden

Una jail operativa muestra el archivo de registro en File list e indica Currently failed: 0. Escriba una contraseña incorrecta tres veces desde otra red. El contador aumentará y la dirección aparecerá en Banned IP list. Si el contador nunca cambia, la causa habitual es logpath: debe ser la ruta del archivo en el host, no la ruta /data/... dentro del contenedor. La segunda causa habitual es la ausencia de X-Real-IP, lo que hace que todos los bloqueos se apliquen a su propio proxy. El resto de la configuración, incluida la jail de SSH que ya debería estar ejecutándose, se encuentra en la guía de fail2ban para Ubuntu 24.04.

La contraseña maestra sigue siendo todo el sistema

El cifrado del lado del cliente significa que la contraseña maestra es la clave. Una contraseña maestra corta en una instancia cuya base de datos ha sido copiada por un atacante no queda protegida por nada de lo descrito en este artículo, porque el atacante puede atacar esa copia sin conexión a la velocidad que permita su hardware. Ningún ajuste del servidor afecta al equipo del atacante.

PASSWORD_ITERATIONS=600000 es el número de iteraciones de KDF que se entrega a los clientes cuando crean una cuenta nueva. Las cuentas existentes conservan el valor con el que se crearon, por lo que aumentarlo no cambia nada para los usuarios que se registraron el año pasado. Deben cambiarlo ellos mismos en la configuración de seguridad de la bóveda web; esto vuelve a cifrar su clave. Infórmeles, porque la interfaz no lo hará.

Después, habilite la autenticación de dos factores para cada cuenta. No protege el texto cifrado, porque la clave de la bóveda procede únicamente de la contraseña maestra. Sí evita que una contraseña robada baste para iniciar sesión y sincronizar una copia. REQUIRE_DEVICE_EMAIL=true añade un paso de confirmación por correo electrónico la primera vez que una cuenta inicia sesión desde un dispositivo no reconocido.

Los backups son donde fallan los almacenes autoalojados

Un tar czf de la carpeta de datos, dejado en el directorio personal del mismo VPS, invalida todos los pasos anteriores. Ese archivo contiene db.sqlite3 con el texto cifrado de todos los usuarios, rsa_key.pem que permite falsificar sesiones de inicio de sesión y config.json con el token de administrador y la contraseña SMTP en texto plano. El acceso de lectura a ese único archivo equivale al acceso de lectura al almacén.

Dos reglas son suficientes. Saque el archivo del servidor. Cífrelo antes de transferirlo.

También existe un problema de integridad. Copiar db.sqlite3 con cp mientras el servicio está en ejecución puede producir un archivo que se esté escribiendo y que no pueda abrirse. No lo descubrirá hasta restaurarlo. Use la propia instantánea de SQLite:

sqlite3 /vw-data/db.sqlite3 ".backup '/tmp/vw-db-backup.sqlite3'"

La restauración, que es la parte que casi nadie prueba, se explica en la guía de backup y restauración de Vaultwarden.

Lo que cede frente a Bitwarden alojado

Una evaluación honesta. El servicio alojado de Bitwarden lo administran personas cuyo trabajo a tiempo completo consiste en operarlo. Cuenta con auditorías externas publicadas y con personal de guardia a las 3am. El autoalojamiento sustituye todo eso por su propio ritmo de aplicación de parches.

Vaultwarden publica las correcciones de seguridad como versiones normales. La versión 1.37.0, publicada el 24 July 2026, es la actual en August 2026, y sus notas piden a los usuarios que actualicen lo antes posible. Una instancia que configuró hace un año y olvidó sigue ejecutando código de hace un año. La etiqueta latest no sirve por sí sola: un contenedor en ejecución conserva la imagen con la que se inició hasta que ejecuta docker compose pull y lo recrea. Configure actualizaciones desatendidas en Ubuntu para los paquetes del host y programe la actualización del contenedor en un recordatorio del calendario que realmente vaya a leer.

La conclusión que debería extraer un lector honesto es la siguiente: la criptografía corresponde al diseño de Bitwarden y es sólida, mientras que todo el riesgo operativo recae en usted. Si aplica los parches y realiza una copia de seguridad en otra ubicación, una instancia de Vaultwarden en un VPS que controle es un lugar razonable para guardar sus contraseñas. Si no va a mantener esos dos hábitos, pague el servicio alojado y dedique su atención a otra tarea. La comparación detallada de funciones está en Vaultwarden frente a Bitwarden autoalojado.

Refuerce el sistema host subyacente al contenedor

Vaultwarden es un proceso más en un sistema Linux, y root en ese sistema puede leer /vw-data independientemente de cómo esté configurada la aplicación. Ejecute el contenedor como un usuario sin privilegios mediante user: "1000:1000" en el archivo compose y asigne la propiedad de la carpeta de datos de forma coherente. Monte como de solo lectura todo lo que el contenedor no necesite modificar mediante :ro. Después cierre el acceso principal: Refuerzo de SSH en un VPS cubre el inicio de sesión únicamente con claves y la desactivación de la autenticación mediante contraseña. Esto impide el ataque básico que puede superar todas las medidas anteriores.

FAQ

¿Puede alguien leer mis contraseñas si roba la base de datos de Vaultwarden?

No directamente. Cada elemento de la bóveda se cifra en el cliente con una clave derivada de la contraseña maestra, por lo que db.sqlite3 contiene texto cifrado. Lo que obtiene de inmediato son la dirección de correo electrónico de cada cuenta, la configuración de KDF, los metadatos de inicio de sesión y del dispositivo, y los secretos de autenticación de dos factores de la tabla twofactor, que se almacenan sin cifrar porque el servidor debe calcular el código esperado. También puede atacar el texto cifrado de la bóveda sin conexión durante todo el tiempo que quiera. Por eso, la longitud de la contraseña maestra es el factor que determina el resultado.

¿Debo usar ADMIN_TOKEN o desactivar por completo la página de administración?

Desactívela si puede, porque la mayoría de las instancias la necesitan una vez para configurar SMTP e invitar a usuarios, y nunca más. Para desactivarla, no establezca ADMIN_TOKEN ni DISABLE_ADMIN_TOKEN, elimine cualquier clave "admin_token" de config.json y vuelva a crear el contenedor. Eliminar sólo la variable de entorno no es suficiente, porque la configuración escrita desde la página de administración se guarda en config.json y tiene prioridad. Si mantiene la página, almacene el token como un hash de Argon2 generado por vaultwarden hash en lugar de una cadena aleatoria en texto plano, y establezca ADMIN_RATELIMIT_MAX_BURST=3.

Mi ADMIN_TOKEN es correcto, pero /admin lo rechaza. ¿Qué ocurre?

Casi siempre se debe a la interpolación de $. Una cadena PHC de Argon2 contiene varios caracteres $, y Docker Compose los expande como variables dentro de un bloque docker-compose.yml environment:. Por eso, el contenedor recibe un valor alterado aunque el archivo parezca correcto. Duplique cada $ como $$ en el archivo compose, o mueva el valor a un archivo .env entre comillas simples, donde no es necesario escapar nada. Después, vuelva a crear el contenedor, porque los cambios de entorno no se aplican al reiniciarlo.

¿Todavía necesito abrir el puerto 3012 para las notificaciones?

No. La compatibilidad con el tráfico WebSocket en el puerto 3012 se eliminó en Vaultwarden 1.31.0 porque las notificaciones pasaron al puerto HTTP principal, y WEBSOCKET_ENABLED y WEBSOCKET_PORT se ignoran desde 1.29.0. La configuración actual es ENABLE_WEBSOCKET, que está establecida en true de forma predeterminada. Cierre 3012 en el firewall y elimínelo del archivo compose. Después, asegúrese de que el reverse proxy reenvíe las cabeceras Upgrade y Connection, porque la sincronización en tiempo real depende ahora de ellas.