Skip to content

TutorialsSelf-hosted apps

How to self-host Vaultwarden with Docker Compose and HTTPS

Run Vaultwarden, a Bitwarden-compatible password server, with Docker Compose behind Caddy, then close sign-ups, hash the admin token and set up backups.

  • Intermediate
  • 35 min read
  • Updated

Tested on: Ubuntu 24.04 LTS, Ubuntu 26.04 LTS, Debian 12, Debian 13

This guide is not available in your language yet, so it is shown in English.

On this page
  1. Prerequisites
  2. Step 1 — Create the project folder
  3. Step 2 — Generate a hashed admin token
  4. Step 3 — Write the environment and Compose files
  5. Step 4 — Start Vaultwarden
  6. Step 5 — Serve Vaultwarden over HTTPS with Caddy
  7. Step 6 — Create your account and close registration
  8. Step 7 — Use the admin page and turn on two-step login
  9. Step 8 — Ban repeated failed logins with fail2ban (optional)
  10. Back up and restore
  11. Update Vaultwarden
  12. Troubleshooting
  13. The web vault shows an error or stays blank over HTTP
  14. You are using a plain text ADMIN_TOKEN which is insecure
  15. WARNING: The argon2id variable is not set
  16. The admin page says it is disabled
  17. Logs and fail2ban show 127.0.0.1 instead of the visitor's IP
  18. Next steps

Vaultwarden is an alternative server for the Bitwarden password manager, written in Rust. It implements the Bitwarden client API, runs as one small container, and works with the regular Bitwarden browser extensions, desktop apps and mobile apps. That makes it a common choice for individuals, families and small teams who want to keep their password vault on a server they control. Vaultwarden is an unofficial project and is not associated with Bitwarden, Inc.

This guide runs the official vaultwarden/server image with Docker Compose, publishes it only on 127.0.0.1, and puts Caddy in front of it for HTTPS, which the web vault requires. You then create your account, close open registration, protect the admin page with an Argon2 hashed token, and set up backups and restores that follow the project's wiki. Banning repeated failed logins with fail2ban is covered as an optional step.

Prerequisites

ResourceMinimum (official)Suggested starting point
CPUNot published1 vCPU
RAMNot published1 GB
DiskNot published10 GB plus room for attachments and backups

The project does not publish minimum requirements. The right-hand column is a conservative starting point for a personal or small-team vault, not a benchmark.

Step 1 — Create the project folder

Keep everything for this app in one folder that your admin user owns:

Bash
sudo mkdir -p /opt/vaultwarden
sudo chown $USER:$USER /opt/vaultwarden
cd /opt/vaultwarden

The vault data will live in /opt/vaultwarden/vw-data, which Docker creates on first start.

Step 2 — Generate a hashed admin token

Vaultwarden has an admin page at /admin where you can invite users, review accounts and change settings. Its password is the ADMIN_TOKEN value. The wiki recommends storing it as an Argon2id PHC string instead of plain text, so that someone who reads your configuration does not learn the password.

First create a long random password and save it in a password manager outside this vault (you may need it when the vault is unavailable):

Bash
openssl rand -base64 32

Then run Vaultwarden's built-in hash command in a temporary container. It asks for the password twice and prints the hash only if both entries match:

Bash
docker run --rm -it vaultwarden/server:latest /vaultwarden hash

The output ends with a line that starts with ADMIN_TOKEN='$argon2id$v=19$m=65540,t=3,p=4$. Copy the value between the single quotes. The default parameters match Bitwarden's own settings; --preset owasp selects the lighter OWASP minimum instead.

Step 3 — Write the environment and Compose files

Store the hash in an .env file next to the Compose file. Following the wiki, wrap the value in single quotes and keep every $ single; Compose then reads it literally:

Bash
nano .env
.env
VAULTWARDEN_ADMIN_TOKEN='$argon2id$v=19$m=65540,t=3,p=4$REPLACE_WITH_YOUR_HASH'
Bash
chmod 600 .env

Now create compose.yaml. It is based on the project's own Compose example, with your domain filled in and sign-ups still allowed so you can create the first account:

YAML
services:
  vaultwarden:
    image: vaultwarden/server:latest
    container_name: vaultwarden
    restart: unless-stopped
    environment:
      DOMAIN: "https://vault.example.com"
      SIGNUPS_ALLOWED: "true"
      ADMIN_TOKEN: ${VAULTWARDEN_ADMIN_TOKEN}
    volumes:
      - ./vw-data/:/data/
    ports:
      - 127.0.0.1:8000:80
  • DOMAIN must be the full HTTPS address users open; some features break without it.
  • 127.0.0.1:8000:80 keeps the container off the public network. Only Caddy on the same server can reach it.
  • The latest tag follows the newest stable release, which the wiki recommends for most users. Version tags such as 1.37.4 and Alpine variants (latest-alpine) also exist.

Check the file for errors before starting:

Bash
docker compose config --quiet

No output means the file is valid. A warning such as The "argon2id" variable is not set means Compose tried to expand the $ signs; check the single quotes in .env.

Step 4 — Start Vaultwarden

Bash
docker compose up -d
docker compose ps
docker compose logs --tail 20
curl -I http://127.0.0.1:8000

docker compose ps shows the container as running (the image has a health check, so it turns healthy after a short while), and curl returns HTTP/1.1 200 OK. Confirm that the admin token arrived exactly as intended, with single $ signs and no extra quotes:

Bash
docker compose exec vaultwarden printenv ADMIN_TOKEN

Step 5 — Serve Vaultwarden over HTTPS with Caddy

Add a site block to /etc/caddy/Caddyfile. The header_up line passes the visitor's real IP address to Vaultwarden, which reads the X-Real-IP header by default; logs, rate limiting and fail2ban depend on it:

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

Reload Caddy and make sure the firewall allows only SSH and web traffic:

Bash
sudo systemctl reload caddy
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
curl -I https://vault.example.com

The last command returns HTTP/2 200 with a valid certificate. Caddy also passes the WebSocket connection that Vaultwarden uses for live sync notifications, with no extra configuration. If you prefer another proxy, see Nginx with Certbot or Traefik.

Step 6 — Create your account and close registration

Open https://vault.example.com in your browser, choose Create account, and set a strong master password. Neither you nor the admin page can recover a forgotten master password, so store a recovery copy safely.

As soon as your account (and any family or team accounts) exist, stop anyone else from registering. Change the line in compose.yaml:

YAML
      SIGNUPS_ALLOWED: "false"

Then recreate the container:

Bash
docker compose up -d

The Create account link disappears from the login page. To add people later, open the admin page, go to Users and use Invite User. Without SMTP no mail is sent; the invited person registers at https://vault.example.com/#/signup with exactly the invited email address. Organization owners can also invite members, because INVITATIONS_ALLOWED defaults to true.

To send invitations, email verification and password hints by email, add SMTP_HOST: "smtp.example.com", SMTP_PORT: "587", SMTP_SECURITY: "starttls", SMTP_FROM, SMTP_USERNAME and SMTP_PASSWORD to the environment section of compose.yaml (keep the password in .env, like the admin token) and recreate the container with docker compose up -d.

Step 7 — Use the admin page and turn on two-step login

Open https://vault.example.com/admin and log in with the password you hashed in Step 2 (not the hash). Admin sessions last 20 minutes by default.

Then protect your own account: in the web vault open Settings, then Security, then Two-step login, and set up an authenticator app. Save the recovery code somewhere outside the vault.

To connect the Bitwarden apps and browser extensions, choose the self-hosted option on the login screen and enter https://vault.example.com as the server URL.

If you rarely need the admin page, you can disable it: remove the ADMIN_TOKEN line (and any admin_token entry in config.json) and recreate the container.

Step 8 — Ban repeated failed logins with fail2ban (optional)

Vaultwarden logs failed logins with the client IP address. fail2ban can read that log and block an address after a few failures. First let Vaultwarden write a log file inside its data folder by adding one variable to the environment section of compose.yaml:

YAML
      LOG_FILE: "/data/vaultwarden.log"
Bash
docker compose up -d
sudo apt install fail2ban

Create the filter /etc/fail2ban/filter.d/vaultwarden.local with the pattern from the wiki:

INI
[INCLUDES]
before = common.conf

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

Create the jail /etc/fail2ban/jail.d/vaultwarden.local:

INI
[vaultwarden]
enabled = true
port = 80,443
filter = vaultwarden
banaction = %(banaction_allports)s
logpath = /opt/vaultwarden/vw-data/vaultwarden.log
backend = auto
maxretry = 3
bantime = 14400
findtime = 14400
Bash
sudo systemctl restart fail2ban
sudo fail2ban-client status vaultwarden

Because Caddy runs on the host, the default ban actions block the visitor before Caddy sees the request. If your reverse proxy runs inside Docker instead, the wiki explains the chain = FORWARD change, and it also has a second filter for failed admin page logins. Vaultwarden does not rotate this log file itself; add a logrotate rule with copytruncate if it grows large.

Back up and restore

All state lives in /opt/vaultwarden/vw-data. The wiki lists what matters:

  • db.sqlite3: the database with almost all vault data (required),
  • attachments/: file attachments (required),
  • config.json: settings saved on the admin page, in plain text (recommended),
  • rsa_key*: the keys that sign login tokens (recommended),
  • sends/: Send attachments, which are meant to be temporary (optional),
  • icon_cache/: cached website icons (not needed).

Do not copy a live db.sqlite3 file on its own. Vaultwarden has a built-in command (since 1.32.1) that writes a consistent copy of the SQLite database next to the original; the Docker image has no sqlite3 binary, so use this command:

Bash
cd /opt/vaultwarden
docker compose exec vaultwarden /vaultwarden backup

It prints a line like Backup to '/data/db_20261009_031500.sqlite3' was successful; inside the container /data is your vw-data folder. Now archive that copy together with the other files and your Compose setup, leaving out the live database files and the icon cache, then delete the temporary copy:

Bash
sudo mkdir -p /opt/backups
sudo tar --exclude='db.sqlite3*' --exclude='icon_cache' -czf /opt/backups/vaultwarden-$(date +%F).tar.gz -C /opt/vaultwarden compose.yaml .env vw-data
sudo rm /opt/vaultwarden/vw-data/db_*.sqlite3

The archive contains secrets (the admin token hash, config.json, the RSA keys and your encrypted vault). Restrict its permissions, run the backup daily with cron or a systemd timer, and copy it off the server, for example to your own S3 storage on another machine.

To restore, stop Vaultwarden, unpack the archive, and turn the dated database copy back into db.sqlite3. The wiki says to delete any existing db.sqlite3-wal first, otherwise the restored database can be corrupted:

Bash
cd /opt/vaultwarden
docker compose down
sudo tar -xzf /opt/backups/vaultwarden-2026-10-09.tar.gz -C /opt/vaultwarden
sudo rm -f vw-data/db.sqlite3 vw-data/db.sqlite3-wal vw-data/db.sqlite3-shm
sudo mv vw-data/db_*.sqlite3 vw-data/db.sqlite3
docker compose up -d

The same steps restore onto a new server after you install Docker and Caddy. Test a restore now and then, and keep the original data until you have confirmed the restored vault works.

Update Vaultwarden

The Bitwarden apps update themselves, so the wiki stresses keeping the server on the latest release. Read the release notes first, because some releases remove old options or change proxy behaviour, take a backup, then pull and recreate:

Bash
cd /opt/vaultwarden
docker compose pull
docker compose up -d
docker compose logs --tail 20
docker image prune

The web vault is bundled with the server image, so it always matches the server version.

Troubleshooting

The web vault shows an error or stays blank over HTTP

The web vault needs a secure context for the Web Crypto API. Open it through https://vault.example.com, not through http:// or the server's IP address, and make sure DOMAIN in compose.yaml is the same HTTPS address.

You are using a plain text ADMIN_TOKEN which is insecure

Either .env still holds a plain password, or a value saved on the admin page overrides it. Check both places, as the wiki suggests: docker compose exec vaultwarden printenv ADMIN_TOKEN and sudo grep admin_token vw-data/config.json. The output must be the $argon2id$ string with single $ signs and no extra quotes.

WARNING: The argon2id variable is not set

Compose tried to expand the $ signs in the hash. Put the value in .env inside single quotes, as in Step 3, or double every $ if you write the hash directly into compose.yaml.

The admin page says it is disabled

ADMIN_TOKEN is empty inside the container. Run docker compose config to see whether the variable from .env reached the service, then recreate the container with docker compose up -d.

Logs and fail2ban show 127.0.0.1 instead of the visitor's IP

The header_up X-Real-IP {remote_host} line is missing from the Caddy site block, or Caddy was not reloaded. If you put Cloudflare's proxy in front, the wiki explains which header to use instead.

Next steps

Frequently asked questions

Is Vaultwarden the official Bitwarden server?

No. Vaultwarden is an independent, unofficial server written in Rust that implements the Bitwarden client API. The project states that it is not associated with Bitwarden or Bitwarden, Inc. The regular Bitwarden apps and browser extensions can connect to it.

Why does the web vault not work over plain HTTP?

The web vault relies on the browser Web Crypto API, which only runs in a secure context. Serve Vaultwarden over HTTPS, for example with Caddy as shown in this guide, and set DOMAIN to the https address.

Do I need an SMTP server for Vaultwarden?

Not for a personal vault. Email is needed for invitation mails, email verification, password hint mails and email-based two-step login. You can add SMTP_HOST, SMTP_FROM and the related variables later.

Can I use MySQL or PostgreSQL instead of SQLite?

Yes. Vaultwarden supports MySQL/MariaDB and PostgreSQL through DATABASE_URL. The default SQLite database is enough for individuals and small teams, and the backup steps in this guide are written for SQLite.

How big a server does Vaultwarden need?

The project does not publish minimum requirements. It is a single lightweight process, so a small Linux VPS with 1 vCPU and 1 GB of RAM is a conservative starting point for a personal, family or small-team vault.

Sources

Generovat heslo

Please confirm