36 min read

How to Self-Host Vaultwarden: Complete Guide to Installation, HTTPS, Security, and Remote Access

Vaultwarden is a lightweight, open-source reimplementation of the Bitwarden server written in Rust. It is fully compatible with all official Bitwarden clients (browser extensions, desktop apps, iOS, Android) and runs on hardware as modest as a Raspberry Pi using under 50 MB of RAM.

Vaultwarden ยท Docker ยท HTTPS ยท Self-Hosting ยท 2026

Build a private password vault with a controlled HTTPS access path

Vaultwarden is an unofficial, community-maintained implementation of the Bitwarden Client API for self-hosted environments. This guide installs it with Docker Compose, binds it to loopback, publishes it through a Localtonet HTTP tunnel, and explains exactly where HTTPS terminates. It also covers account hardening, client configuration, Raspberry Pi storage, encrypted off-host backups, isolated restoration tests, migration, updates, and troubleshooting.

๐Ÿณ Persistent Docker deployment ๐Ÿ”’ Trusted public HTTPS ๐ŸŒ No inbound router port forwarding ๐Ÿ’พ Encrypted, tested recovery workflow
Self-hosted Vaultwarden server connected securely to laptop and phone clients.
Vaultwarden runs locally while official clients connect through a controlled HTTPS access path.

What Vaultwarden is, and what it is not

Vaultwarden, formerly called bitwarden_rs, is an alternative server implementation of the Bitwarden Client API written in Rust. It is not produced by or affiliated with Bitwarden, Inc. The project provides its own server, a modified web vault bundled with its container images, and support for many familiar Bitwarden capabilities.

The Vaultwarden project describes its API implementation as nearly complete. That distinction matters. Official Bitwarden browser extensions, mobile applications, and desktop clients are commonly used with Vaultwarden, but compatibility should not be treated as universal or permanent. A newly released client feature can depend on an API behavior that Vaultwarden has not implemented yet. Test the clients and workflows you need before treating an upgrade as production-ready.

Vaultwarden recommends its published container images for installation. Community packages and source builds are alternatives, but a community package may lag behind current releases or change configuration conventions. This tutorial uses the official Vaultwarden container image with Docker Compose and a persistent host directory.

Vaultwarden has a separate support path

Do not contact official Bitwarden support about a Vaultwarden server. Report Vaultwarden problems through the Vaultwarden GitHub project, its discussions, or the Vaultwarden community forum. Record the Vaultwarden version, client version, relevant sanitized logs, and whether the same operation works in the bundled web vault.

Deployment model Operator responsibility Support relationship Recovery responsibility
Vaultwarden self-hosted You maintain the host, container, storage, HTTPS access path, updates, monitoring, and backups. Unofficial server with community support. You preserve the database, attachments, Sends, signing keys, configuration, and required secrets.
Official Bitwarden self-hosted You operate an official deployment using a currently supported Bitwarden installation path. Official server and client relationship. Support depends on the applicable offering. You remain responsible for infrastructure and the documented recovery process.
Hosted password manager The provider operates the service infrastructure. You manage accounts, recovery options, and endpoint security. Provider-controlled server and client release cycle. Provider resilience does not replace account recovery planning or protected exports.

Self-hosting gives you operational control, but availability and recovery become your responsibility. A database on hardware you own is not automatically private, secure, or resilient. Host compromise, weak credentials, exposed administration, an untrusted backup destination, or an untested restoration process can still put the vault at risk.

HTTPS, secure contexts, and the trust boundary

HTTPS and tunnel path from Bitwarden clients to a private Vaultwarden container.
HTTPS protects the client-facing connection, while the outbound tunnel carries traffic to the private host.

Vaultwarden's web vault uses browser cryptography and requires HTTPS and a secure context. The project calls this out in its installation guidance, and the browser rules are described in the MDN secure-context documentation. Browsers generally treat a valid HTTPS origin as potentially trustworthy. Loopback origins such as http://localhost also receive special secure-context treatment in common browsers, which is useful for same-machine testing.

An ordinary LAN address such as http://192.168.1.20 is not equivalent to localhost. It should not be used as the remote web-vault address. The certificate presented to remote clients must also be valid for the requested hostname and trusted by the device. An expired certificate, hostname mismatch, incomplete certificate chain, or untrusted private certificate authority can produce warnings or prevent native applications from connecting.

๐Ÿ–ฅ๏ธ Loopback testing http://localhost:8080 can confirm that Vaultwarden responds on the server itself. It is not the remote client address.
๐Ÿ” Trusted public HTTPS Remote browsers and applications should use an HTTPS URL with a valid certificate for the exact hostname.
๐Ÿ“œ Private certificates A private certificate requires its issuing authority to be installed and trusted correctly on every client device.
๐ŸŒ Localtonet endpoint A Localtonet HTTP tunnel provides a public HTTPS address while the local Vaultwarden target remains bound to loopback.

HTTPS options

Method Certificate model Inbound network requirement Operational responsibility
Reverse proxy with a public certificate A reverse proxy such as Caddy or Nginx presents the certificate. Depends on routing and certificate validation. DNS-based validation can avoid inbound validation ports. You maintain DNS, certificate renewal, proxy configuration, and any router or firewall exposure.
Private certificate authority You issue certificates and distribute the trust anchor. No public certificate validation is required. Every operating system, browser, and native client must trust the issuing authority correctly.
Localtonet HTTP tunnel The public endpoint presents HTTPS and forwards requests through an outbound tunnel. No inbound router port forwarding or public IP address is required. Vaultwarden, the selected Localtonet client, and the tunnel must all remain running.

Where TLS terminates in this tutorial

This deployment binds Vaultwarden to 127.0.0.1:8080. A remote Bitwarden client connects to the assigned Localtonet HTTPS endpoint. HTTPS is terminated at the tunnel edge, and traffic is carried through the outbound Localtonet tunnel to the Localtonet client. That client connects to http://127.0.0.1:8080 on the same host.

This is not TLS terminating inside Vaultwarden. The trust boundary includes the public endpoint, relay path, Localtonet client process, host operating system, Docker runtime, and Vaultwarden container. Keeping our client and Vaultwarden on the same trusted machine confines the final HTTP connection to loopback. If the Localtonet client forwards to another machine on a LAN, the final hop crosses that LAN in clear HTTP unless you add and validate a separate TLS layer.

Never ignore a certificate warning

A password-manager client should not be trained to accept an expired, mismatched, or untrusted certificate. Correct the hostname, certificate chain, trust configuration, or device clock before signing in.

Prerequisites and installation scope

This tutorial covers a maintained 64-bit Linux host, including a compatible 64-bit Raspberry Pi deployment, with Docker Engine and Docker Compose v2. Install Docker using the supported instructions for your distribution. Package names, repository setup, and architecture support differ between operating systems.

  • A maintained Linux distribution supported by Docker Engine.
  • Docker Engine and the Docker Compose plugin.
  • A non-root administrative account with carefully controlled Docker access.
  • A persistent filesystem for the database, attachments, Sends, keys, and configuration.
  • A Localtonet account and Localtonet client on the same host for this loopback design.
  • Separate storage for the Vaultwarden admin password, user master passwords, recovery codes, device token, SSH backup key, and backup decryption key.

Use Docker's operating-system-specific Engine instructions and Compose installation instructions. Then verify the installation:

docker --version
docker compose version
docker info

All commands should complete without an unexpected connection or permission error. Docker documents that membership in the Docker group grants root-level privileges. Review the Docker non-root access warning before adding users to that group.

Separate every important credential

The Vaultwarden administration password and each user's vault master password must be unique. Do not reuse either as the host login, Localtonet account password, backup key, SSH key passphrase, or disk-encryption credential. Keep two-factor recovery material outside the vault so it remains available during an outage.

Install Vaultwarden with Docker Compose

Docker Compose project showing Vaultwarden running with persistent storage.
The Compose deployment separates the replaceable container from persistent Vaultwarden data.

The following deployment follows the container pattern published by Vaultwarden: persistent storage mounted at /data, a loopback-only host port, a public domain setting, and a restart policy. Review the current container-tag guidance and release notes before production use. The moving latest tag is convenient during initial setup, but it should not be pulled automatically without review.

1

Create a protected project directory

Create the Compose project and persistent data directory under your administrative account.

mkdir -p "$HOME/vaultwarden/vw-data"
cd "$HOME/vaultwarden"
chmod 700 "$HOME/vaultwarden" "$HOME/vaultwarden/vw-data"
2

Generate an Argon2 administration hash

Run Vaultwarden's hash utility and enter an administration password that is different from every user's master password.

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

The command returns an Argon2 PHC string beginning with $argon2. Keep the original password in your credential store. The PHC string itself is not the password entered at /admin.

3

Create the protected environment file

Place the complete generated PHC value inside single quotes. Replace the example value before starting the container.

umask 077
cat > .env <<'EOF'
ADMIN_TOKEN='$argon2id$v=19$m=65540,t=3,p=4$replace_with_generated_salt$replace_with_generated_hash'
SIGNUPS_ALLOWED=true
TZ=Etc/UTC
EOF
chmod 600 .env

Keep the single quotes in the file. Do not paste real credentials into screenshots, tickets, public repositories, or shell commands that will be retained in shared history.

4

Create the Compose definition

Save the following file as compose.yaml. The loopback binding prevents direct access through the host's LAN or public interfaces.

services:
  vaultwarden:
    image: vaultwarden/server:latest
    container_name: vaultwarden
    restart: unless-stopped
    env_file:
      - .env
    volumes:
      - ./vw-data:/data
    ports:
      - "127.0.0.1:8080:80"

For controlled production updates, replace the moving image reference with a release tag or digest selected after reviewing current Vaultwarden guidance.

5

Validate and start Vaultwarden

Validate the Compose file, start the container, and inspect its initial state.

docker compose config --quiet
docker compose up -d
docker compose ps
docker compose logs --tail=100 vaultwarden

Do not publish expanded output from docker compose config. Resolved output can reveal environment values.

6

Test local reachability

Verify the host can reach the loopback listener before adding remote access.

curl -I http://127.0.0.1:8080/
docker compose ps
docker compose logs --tail=100 vaultwarden

An HTTP response proves the container and published host port are connected. It does not prove public HTTPS, authentication, synchronization, or recovery.

Vaultwarden's configuration overview explains its broader environment and administrative configuration model. Do not add settings copied from old examples without checking whether they still apply to the deployed version.

Create the Localtonet HTTPS access path

Our platform exposes a local service through an outbound connection from the selected client device to a Localtonet relay server. This avoids inbound router port forwarding, firewall changes, VPN setup, and the need for a public IP address. For this deployment, the HTTP tunnel target is 127.0.0.1 on port 8080.

Follow the current Localtonet HTTP tunnel documentation alongside the sequence below. Dashboard labels, available relay servers, operating-system installers, and plan behavior can change, so select current values rather than copying old server codes or screenshots.

1

Install and run the Localtonet client

Install the current client for the Vaultwarden host and run it on that same machine. Keeping the client local to Vaultwarden preserves the loopback-only target.

2

Authenticate or select the device

Use the device-specific authentication token for the client that will run the tunnel. Treat that token as a secret and do not place it in the Vaultwarden Compose file.

3

Open the HTTP tunnel configuration

Create an HTTP tunnel and select the required process type: Random Sub Domain, Custom Sub Domain where supported, or Custom Domain. These process types serve the same local content through a public HTTPS address.

4

Select the authentication token and relay server

Select the token belonging to the client on the Vaultwarden host, then select an available server or region displayed by the current dashboard. Do not hardcode a server code from another deployment.

5

Enter the local target

Set the local IP address to 127.0.0.1 and the local port to 8080. These values must match the host port in compose.yaml.

6

Create and start the tunnel

Create the configuration, then press Start. Creating a tunnel does not mean it is running. Record the exact assigned HTTPS URL after the dashboard shows the tunnel as started.

7

Set Vaultwarden's public domain idempotently

Replace the placeholder URL below. The command removes every existing DOMAIN assignment before writing exactly one current value.

cd "$HOME/vaultwarden"
PUBLIC_URL='https://your-exact-assigned-hostname'
TMP_FILE="$(mktemp "$PWD/.env.XXXXXX")"

awk '!/^[[:space:]]*DOMAIN=/' .env > "$TMP_FILE"
printf 'DOMAIN=%s\n' "$PUBLIC_URL" >> "$TMP_FILE"
chmod 600 "$TMP_FILE"
mv "$TMP_FILE" .env

docker compose up -d
docker compose logs --tail=100 vaultwarden

Running this procedure again replaces the previous value instead of appending duplicates. Use the exact assigned origin without adding /admin or another path.

Keep the Localtonet client available after reboot

Vaultwarden's restart: unless-stopped policy can restart its container when Docker returns, but that policy does not manage the Localtonet client. Remote access also depends on our client starting, authenticating, connecting, and resuming the selected tunnel after a host reboot.

The supported startup mechanism varies by operating system, installation type, and Localtonet client version. Use the startup or service option supplied by the current client package for your platform. If the package does not provide one, manage the client through the operating system's supported service manager using the exact executable and invocation shown by the installed client. This guide does not invent a universal service command because paths and arguments vary.

Perform a planned reboot test before relying on the service:

  1. Confirm the Vaultwarden container, Localtonet client, and HTTP tunnel are all running.
  2. Reboot the host during a maintenance window.
  3. Confirm the data filesystem is mounted before Vaultwarden starts.
  4. Confirm the container returns with the original vault data.
  5. Confirm the selected Localtonet device reconnects.
  6. Confirm the existing tunnel is started and the assigned URL works from another network.
Remote availability has multiple active dependencies

Vaultwarden must be running, the selected Localtonet client must remain connected, and the tunnel must remain started. Relay availability and plan-specific timeout or availability behavior also apply. Confirm current plan details before treating the endpoint as continuously available.

Outer authentication can break native clients

Where supported by the selected configuration, IP restrictions can reduce exposure. Test every required client network because a home-only allowlist can block a phone after it switches to mobile data.

Be cautious with an additional Basic Authentication, SSO, or interactive login layer in front of Vaultwarden. A browser may display the extra prompt, while a Bitwarden native client may not know how to complete it for API and synchronization requests. Test login, synchronization, attachments, Sends, and every required client before retaining an outer challenge.

First login and security hardening

Create the first account and close registration

Open the assigned HTTPS URL and confirm that the browser reports a valid certificate for the exact hostname. Create the intended initial accounts with unique master passwords. When enrollment is complete, change SIGNUPS_ALLOWED to false and recreate the container:

cd "$HOME/vaultwarden"
sed -i 's/^SIGNUPS_ALLOWED=.*/SIGNUPS_ALLOWED=false/' .env
docker compose up -d
docker compose logs --tail=100 vaultwarden

Use a private browser window to confirm public registration is unavailable. Do not rely only on the environment file. Settings persisted through the administration interface can affect effective behavior, so verify the result from an unauthenticated session.

Protect or disable administration

The administration endpoint controls server settings and user administration. At /admin, enter the original administration password used to generate the Argon2 hash, not the PHC string stored in .env. Review the project's administration-page documentation before changing the token or exposing the endpoint.

If you do not need ongoing web administration, consider disabling it after completing the required configuration. If you retain it, use a unique credential, restrict exposure where compatible with your clients, and review access attempts. Do not assume the administrator can decrypt users' vaults or reveal forgotten master passwords.

Enable two-factor authentication

Vaultwarden implements multiple two-step login methods, including authenticator codes and FIDO2 WebAuthn. Availability and behavior can vary by configuration and client version. Enroll the selected method, preserve its recovery material outside the vault, and test at least two client sessions before declaring the setup complete.

๐Ÿ”‘ Protect secrets Keep .env, Localtonet device tokens, SMTP credentials, SSH keys, backup keys, and recovery codes out of repositories and logs.
๐Ÿ›ก๏ธ Patch the host Apply operating-system, Docker, Vaultwarden, and Localtonet client updates through a reviewed maintenance process.
๐Ÿšช Minimize listeners Keep Vaultwarden bound to 127.0.0.1 unless a documented architecture requires a broader listener.
๐Ÿ‘ค Limit administrators Restrict host and Docker access to trusted users. Docker control can provide root-equivalent host access.
๐Ÿ’พ Encrypt backups Backups contain sensitive records, metadata, configuration, attachments, and signing keys even when vault items are encrypted.
๐Ÿ“‹ Review logs safely Inspect errors without publishing complete logs that may contain account identifiers, addresses, or configuration details.

Why this guide does not add a Fail2Ban sidecar

A reliable Fail2Ban design requires the application log to contain the real client IP, a verified trusted-proxy configuration, and a ban action that can block the actual traffic path. With a relay or reverse proxy in front of Vaultwarden, the observed address may be a proxy address instead of the user.

A filtering container may also require powerful network capabilities such as NET_ADMIN or NET_RAW. Those privileges increase its impact if compromised. Do not deploy a copied Fail2Ban configuration until you have verified client-IP propagation, trusted proxy addresses, the exact log format, the filtering image and version, and the effect of a controlled test ban.

Connect and test Bitwarden clients

Install an official Bitwarden client from its normal distribution channel. Before logging in, choose the self-hosted server option and enter the exact Localtonet HTTPS origin. Menu labels can change between browser, desktop, iOS, and Android releases.

1

Set the self-hosted server URL

Enter the assigned HTTPS origin without adding /admin, an API route, or another private path. Confirm the client accepts the certificate without a warning.

2

Log in with the vault account

Use the account email and master password, not the Vaultwarden administration password. Complete the configured second factor.

3

Test synchronization both ways

Create a harmless test item in one client, synchronize another client, edit it there, and verify the first client receives the change.

4

Test required advanced features

If you depend on organizations, collections, attachments, Sends, WebAuthn, emergency access, or another advanced workflow, test it with the exact client versions you plan to use.

Verify the complete deployment

A successful container start is only the first check. Complete these acceptance tests before importing production credentials.

Test Method Expected result
Local service Request http://127.0.0.1:8080 and inspect Compose state. Vaultwarden responds and the container remains running.
Public certificate Open the assigned URL from another network and inspect the certificate. No trust, expiry, hostname, or chain warning appears.
Authentication Log in through the web vault and at least one native client. The master password and configured second factor work.
Synchronization Create and edit a harmless item across two clients. Both clients receive the changes.
Registration closure Open a private session after disabling signups. An unauthenticated visitor cannot create an unrestricted account.
Host reboot Perform a planned reboot and inspect storage, Docker, our client, and the tunnel. The original vault returns without an empty data directory.
Failure behavior Separately stop the tunnel, client, and container, restoring each after the test. Remote access fails when expected and returns after the missing dependency is restored.
Recovery Restore an encrypted backup into an isolated project. Test records, attachments, Sends, settings, and keys are present without changing production.

Raspberry Pi and safe external storage

Support for a particular Raspberry Pi depends on the current Vaultwarden image architecture, the installed operating system, Docker support, and reliable storage. Check the host before pulling an image:

uname -m
docker info --format '{{.Architecture}}'
lsblk -f

An ARM64 installation commonly reports aarch64. Verify current image and Docker support rather than relying on old model-specific memory claims.

Move the existing data directory

Never assume an external disk is /dev/sda1, never guess its filesystem type, and never change the Compose mount before copying the existing data. If Docker binds an empty directory, Vaultwarden can appear to contain an empty vault.

  1. Create and verify a current encrypted backup.
  2. Identify the destination by filesystem UUID, label, size, and type using lsblk -f, blkid, and findmnt.
  3. Stop Vaultwarden with docker compose down.
  4. Mount the verified filesystem at a stable location such as /mnt/vault-storage.
  5. Copy the complete data directory with metadata preserved.
  6. Add an appropriate UUID-based entry to /etc/fstab and test it with mount -a.
  7. Change the Compose data source only after findmnt confirms the destination is mounted.
  8. Retain the original directory until the new storage survives a reboot and restoration test.
cd "$HOME/vaultwarden"
docker compose down

sudo mkdir -p /mnt/vault-storage
sudo mount /dev/verified-partition /mnt/vault-storage
findmnt /mnt/vault-storage

sudo mkdir -p /mnt/vault-storage/vw-data
sudo rsync -aHAX --numeric-ids \
  "$HOME/vaultwarden/vw-data/" \
  /mnt/vault-storage/vw-data/

sudo rsync -aHAXn --delete --numeric-ids \
  "$HOME/vaultwarden/vw-data/" \
  /mnt/vault-storage/vw-data/

After testing the persistent mount, change the Compose volume:

volumes:
  - type: bind
    source: /mnt/vault-storage/vw-data
    target: /data
    bind:
      create_host_path: false

The create_host_path: false setting prevents Compose from silently creating a missing source directory. It does not by itself prove that the correct filesystem is mounted. A stale directory could still exist beneath the mount point.

Require the mount before starting Vaultwarden

For an external data disk, the safest startup design is to let systemd start the Compose project only after the mount is ready. Change the Compose restart policy to restart: "no" so Docker does not independently restart an old container before systemd verifies the filesystem.

Create a unit whose paths and user match the deployment:

[Unit]
Description=Vaultwarden Compose service
Requires=docker.service
After=docker.service
RequiresMountsFor=/mnt/vault-storage
ConditionPathIsMountPoint=/mnt/vault-storage

[Service]
Type=oneshot
RemainAfterExit=yes
User=REPLACE_WITH_LINUX_USER
WorkingDirectory=/home/REPLACE_WITH_LINUX_USER/vaultwarden
ExecStart=/usr/bin/docker compose up -d
ExecStop=/usr/bin/docker compose down
TimeoutStartSec=0

[Install]
WantedBy=multi-user.target

Save it as /etc/systemd/system/vaultwarden-compose.service after replacing both user placeholders and confirming the Docker path with command -v docker. Then validate and enable it:

sudo systemd-analyze verify /etc/systemd/system/vaultwarden-compose.service
sudo systemctl daemon-reload
sudo systemctl enable vaultwarden-compose.service
sudo systemctl start vaultwarden-compose.service
sudo systemctl status vaultwarden-compose.service

Perform two controlled tests. First, reboot with the drive available and confirm the original vault returns. Second, during a maintenance window, stop the service, unmount or disconnect the test drive, and confirm the unit refuses to start Vaultwarden. Do not create or edit vault records during the missing-storage test.

A missing disk must cause a visible failure

Starting successfully against an empty root-filesystem directory is more dangerous than failing to start. Monitor the mount, service state, disk capacity, and known vault records after every storage or boot change.

Create encrypted, off-host Vaultwarden backups

Vaultwarden backup and restore test covering the complete persistent data set.
A usable backup includes the complete persistent data set and is proven through an isolated restoration test.

A recoverable set contains more than db.sqlite3. Review the current Vaultwarden backup guidance before changing the workflow for a new release.

Component Purpose Recovery treatment
db.sqlite3 Users, vault records, organizations, and application state. Create a consistent SQLite snapshot.
attachments/ Files attached to vault items. Copy recursively with the matching database snapshot.
sends/ Files associated with Sends. Copy recursively when present.
rsa_key* Server signing-key material. Preserve every matching file and its permissions.
config.json Settings persisted through administration. Restore deliberately because persisted settings can affect effective configuration.
.env and compose.yaml Deployment settings and the administration hash. Include only inside the encrypted archive.

Prepare encryption and off-host storage

This example uses age recipient encryption and OpenSSH transfer. Generate and protect the age identity on a separate trusted recovery system. Store only its public recipient on the Vaultwarden host. If the server never holds the decryption identity, compromise of the host does not automatically provide the backup decryption key.

Also prepare a dedicated SSH account and destination on another system. Pin and verify its host key before automation, restrict the account and key as appropriate for your environment, and ensure the destination is not continuously mounted as a writable local filesystem. Install sqlite3, rsync, age, and an OpenSSH client from your operating system's trusted package source.

sqlite3 --version
rsync --version
age --version
ssh -V

Create the backup configuration

Create $HOME/.config/vaultwarden-backup.env with values for your environment:

AGE_RECIPIENT='age1replace_with_your_public_recipient'
BACKUP_HOST='backup-user@backup-host'
BACKUP_DIR='/srv/backups/vaultwarden'
SSH_KEY='/home/replace-user/.ssh/vaultwarden-backup'
LOCAL_RETENTION_DAYS='14'
REMOTE_RETENTION_DAYS='90'
chmod 600 "$HOME/.config/vaultwarden-backup.env"

The age recipient is public, but the SSH key and deployment files are sensitive. Keep the age private identity off the production host and preserve it in at least two protected recovery locations.

Run a complete encrypted backup

The following script briefly stops Vaultwarden so the database, attachments, Sends, keys, and configuration all represent one quiet point in time. It creates a consistent SQLite snapshot, encrypts the complete set, transfers the encrypted archive and checksum off-host, applies retention, and writes a success marker only after transfer completes.

#!/bin/sh
set -eu
umask 077

PROJECT="$HOME/vaultwarden"
DATA="$PROJECT/vw-data"
ROOT="$HOME/vaultwarden-backups"
CONFIG="$HOME/.config/vaultwarden-backup.env"
STAMP="$(date -u +%Y%m%dT%H%M%SZ)"
WORK="$(mktemp -d "$ROOT/.work.XXXXXX")"
SET="$WORK/set"
ARCHIVE="$ROOT/vaultwarden-$STAMP.tar.gz.age"
CHECKSUM="$ARCHIVE.sha256"
STOPPED=0

. "$CONFIG"

cleanup() {
  if [ "$STOPPED" -eq 1 ]; then
    cd "$PROJECT"
    docker compose start vaultwarden >/dev/null 2>&1 || true
  fi
  rm -rf "$WORK"
}
trap cleanup EXIT HUP INT TERM

command -v sqlite3 >/dev/null
command -v rsync >/dev/null
command -v age >/dev/null
command -v ssh >/dev/null
command -v scp >/dev/null

mkdir -p "$ROOT" "$SET/data"
cd "$PROJECT"

docker compose stop vaultwarden
STOPPED=1

sqlite3 "$DATA/db.sqlite3" ".backup '$SET/data/db.sqlite3'"

for directory in attachments sends; do
  if [ -d "$DATA/$directory" ]; then
    mkdir -p "$SET/data/$directory"
    rsync -aH --numeric-ids \
      "$DATA/$directory/" \
      "$SET/data/$directory/"
  fi
done

if [ -f "$DATA/config.json" ]; then
  cp -p "$DATA/config.json" "$SET/data/config.json"
fi

for key in "$DATA"/rsa_key*; do
  if [ -e "$key" ]; then
    cp -p "$key" "$SET/data/"
  fi
done

cp -p "$PROJECT/.env" "$SET/.env"
cp -p "$PROJECT/compose.yaml" "$SET/compose.yaml"

sqlite3 "$SET/data/db.sqlite3" "PRAGMA integrity_check;" |
  grep -qx 'ok'

docker compose start vaultwarden
STOPPED=0

tar -C "$WORK" -czf - set |
  age -r "$AGE_RECIPIENT" -o "$ARCHIVE"

sha256sum "$ARCHIVE" > "$CHECKSUM"

scp -i "$SSH_KEY" "$ARCHIVE" "$CHECKSUM" \
  "$BACKUP_HOST:$BACKUP_DIR/"

ssh -i "$SSH_KEY" "$BACKUP_HOST" \
  "find '$BACKUP_DIR' -type f -name 'vaultwarden-*.tar.gz.age*' -mtime +$REMOTE_RETENTION_DAYS -delete"

find "$ROOT" -type f \
  \( -name 'vaultwarden-*.tar.gz.age' -o -name 'vaultwarden-*.tar.gz.age.sha256' \) \
  -mtime +"$LOCAL_RETENTION_DAYS" -delete

date -u +%Y-%m-%dT%H:%M:%SZ > "$ROOT/last-success"
printf 'Backup completed: %s\n' "$ARCHIVE"

Save it outside the web service directory, for example as $HOME/bin/backup-vaultwarden, then restrict and test it:

chmod 700 "$HOME/bin/backup-vaultwarden"
"$HOME/bin/backup-vaultwarden"
cat "$HOME/vaultwarden-backups/last-success"

Confirm the encrypted archive and checksum exist on the remote destination before scheduling the job. A local encrypted copy is useful for quick recovery, but it is not a substitute for the off-host copy.

Schedule and monitor the backup

A simple user crontab can run the script daily. The following example runs at 02:15 and stores output in a protected log:

crontab -e
15 2 * * * /home/replace-user/bin/backup-vaultwarden >> /home/replace-user/vaultwarden-backups/backup.log 2>&1

Replace the username with the deployment account. Cron execution alone is not monitoring. Configure your existing monitoring system to alert when the command fails, when the remote destination does not receive a new archive, or when the success marker is older than the expected interval. This check exits unsuccessfully when no successful backup has completed within 25 hours:

MARKER="$HOME/vaultwarden-backups/last-success"
test -f "$MARKER"
test -z "$(find "$MARKER" -mmin +1500 -print)"

Retention values are examples, not universal policy. Choose local and remote retention according to your recovery objectives, storage capacity, legal requirements, and the time it could take to notice delayed corruption or accidental deletion.

Encrypted records do not make a disclosed backup harmless

A backup can contain account metadata, attachments, Sends, signing keys, administrative settings, and deployment secrets. Encrypted vault records can also become an offline password-guessing target. Protect the archive, transfer credentials, age identity, and backup destination separately.

Perform a truly isolated restore drill

An isolated test must not stop, replace, or reuse the production project. Use a separate directory, container name, bind port, and data directory. Do not attach a Localtonet tunnel to it. The internal Docker network below prevents outbound network access, which blocks accidental mail or other external side effects even if restored configuration contains SMTP settings.

1

Verify and decrypt one archive

Perform this on a trusted test host that has access to the protected age identity.

mkdir -p "$HOME/vaultwarden-restore-test/source"
cd "$HOME/vaultwarden-restore-test"

sha256sum -c /path/to/vaultwarden-backup.tar.gz.age.sha256

age --decrypt \
  -i /protected/path/to/age-identity.txt \
  -o backup.tar.gz \
  /path/to/vaultwarden-backup.tar.gz.age

tar -xzf backup.tar.gz -C source
sqlite3 source/set/data/db.sqlite3 "PRAGMA integrity_check;"

The integrity check must return ok.

2

Create a separate restore data directory

Copy required and optional components conditionally so absent attachments, Sends, configuration, or key files do not make the procedure fail.

cd "$HOME/vaultwarden-restore-test"
mkdir -p restore-data
chmod 700 restore-data

cp -p source/set/data/db.sqlite3 restore-data/

for directory in attachments sends; do
  if [ -d "source/set/data/$directory" ]; then
    mkdir -p "restore-data/$directory"
    rsync -aH --numeric-ids \
      "source/set/data/$directory/" \
      "restore-data/$directory/"
  fi
done

if [ -f source/set/data/config.json ]; then
  cp -p source/set/data/config.json restore-data/
fi

for key in source/set/data/rsa_key*; do
  if [ -e "$key" ]; then
    cp -p "$key" restore-data/
  fi
done
3

Create an isolated Compose project

Use the Vaultwarden image version recorded for the selected backup whenever possible. Replace the image placeholder before starting.

services:
  vaultwarden-restore:
    image: vaultwarden/server:REPLACE_WITH_RECORDED_TAG
    container_name: vaultwarden-restore-test
    restart: "no"
    environment:
      DOMAIN: "http://localhost:18080"
      SIGNUPS_ALLOWED: "false"
    volumes:
      - ./restore-data:/data
    ports:
      - "127.0.0.1:18080:80"
    networks:
      - restore-only

networks:
  restore-only:
    internal: true

Save it as compose.yaml inside $HOME/vaultwarden-restore-test. Do not use the production .env, container name, port, data directory, or tunnel.

4

Start and inspect the isolated vault

The test vault is reachable only from the host at the loopback port.

cd "$HOME/vaultwarden-restore-test"
docker compose config --quiet
docker compose up -d
docker compose ps
docker compose logs --tail=200 vaultwarden-restore
curl -I http://127.0.0.1:18080/

Open http://localhost:18080 on that same test host. Localhost receives special secure-context treatment in common browsers.

5

Validate and destroy the drill

Confirm representative records, account login, attachments, Sends, organizations, and relevant settings. Record the backup date, test date, image version, result, and any manual action. Then remove the test container and securely handle the decrypted working files.

cd "$HOME/vaultwarden-restore-test"
docker compose down
rm -f backup.tar.gz

Retain the written test result, not an unnecessary decrypted copy. Repeat the drill on a recurring schedule and after material changes to storage, encryption, backup scripts, or Vaultwarden versions.

Production recovery is a separate procedure

A production recovery is disruptive and should be performed only after preserving the failed data directory. Stop the production service, verify and decrypt the selected archive, check the database, move the failed data aside, restore each component conditionally, verify ownership against the preserved directory, and start the previously tested image.

cd "$HOME/vaultwarden"
docker compose down

STAMP="$(date -u +%Y%m%dT%H%M%SZ)"
mv vw-data "vw-data.before-restore.$STAMP"
mkdir vw-data
chmod 700 vw-data

SOURCE="/protected/restore-source/set/data"

cp -p "$SOURCE/db.sqlite3" vw-data/

for directory in attachments sends; do
  if [ -d "$SOURCE/$directory" ]; then
    mkdir -p "vw-data/$directory"
    rsync -aH --numeric-ids \
      "$SOURCE/$directory/" \
      "vw-data/$directory/"
  fi
done

if [ -f "$SOURCE/config.json" ]; then
  cp -p "$SOURCE/config.json" vw-data/
fi

for key in "$SOURCE"/rsa_key*; do
  if [ -e "$key" ]; then
    cp -p "$key" vw-data/
  fi
done

sqlite3 vw-data/db.sqlite3 "PRAGMA integrity_check;"
ls -la vw-data
docker compose up -d
docker compose logs --tail=200 vaultwarden

Do not assign a guessed numeric user ID or make the data directory world-writable. Compare ownership and permissions with the preserved pre-restore directory or a known-good deployment. Restore the protected deployment files only when required and only from the encrypted set.

Migrate an existing Bitwarden vault carefully

Review Bitwarden's current export documentation before creating a migration file. Encrypted JSON exports can use different protection models. A password-protected encrypted export is designed to be unlocked with its export password and is appropriate when a portable encrypted file is required. An account-restricted encrypted export is tied to the originating account and should not be assumed portable to another server.

If the target importer does not support the selected encrypted format, a plaintext export may be required. Perform that operation only on a trusted, patched device. Use encrypted local storage, pause unnecessary synchronization or cloud-backup software, import promptly, and securely remove temporary plaintext copies afterward.

Review the current Bitwarden import guidance for the selected client and format. Exports may not contain every service object. Attachments and Sends can require separate handling, and organization data has its own rules.

After import, compare item counts and manually inspect representative logins, secure notes, identities, cards, custom fields, organizations, collections, attachments, and two-factor records. Test synchronization on at least two clients. Do not delete or close the source account until the destination has passed these checks and a new encrypted backup has completed.

Routine operation, updates, and rollback

Recurring checks

  • Confirm the Vaultwarden container is running and review new startup or authentication warnings.
  • Confirm the selected Localtonet device is connected and the existing tunnel is started.
  • Test the public URL and certificate from outside the host.
  • Confirm backups completed, were encrypted, reached the off-host destination, and remain within retention policy.
  • Monitor the data filesystem, Docker storage, logs, attachments, and backup staging capacity.
  • Confirm public registration remains closed.
  • Perform recurring isolated restoration tests and record their results.
  • Test startup after planned host, Docker, storage, or Localtonet client changes.

Review updates before applying them

A moving image tag changes when pulled. Before an update, read Vaultwarden release notes, review compatibility reports relevant to your clients, create a verified backup, and record the current image reference:

cd "$HOME/vaultwarden"
docker compose images
docker image inspect vaultwarden/server:latest \
  --format '{{index .RepoDigests 0}}'
docker compose logs --tail=100 vaultwarden

During a maintenance window, pull the reviewed image and recreate the service:

docker compose pull vaultwarden
docker compose up -d vaultwarden
docker compose ps
docker compose logs --tail=200 vaultwarden

Repeat certificate, login, synchronization, registration, attachment, and tunnel tests. Do not automate unattended image pulls for a password manager unless you have tested monitoring and rollback.

Plan rollback before updating

Retain the previous image reference and complete pre-update backup. Database migrations can make a simple image downgrade unsafe. A rollback may require stopping the service, restoring the complete pre-update data set and secrets, and starting the previously tested image. Prove that process in an isolated environment before an emergency.

Stopped component Observed behavior Recovery action
Vaultwarden container The tunnel may remain present, but the target cannot serve requests. Inspect Compose state, storage, and application logs before restarting.
Localtonet client The relay cannot reach the local target through that device. Restore the selected client connection and verify startup persistence.
HTTP tunnel The public endpoint is unavailable even though Vaultwarden may be healthy locally. Start the existing tunnel and verify its selected device and server.
Host or data filesystem Both services disappear, or Vaultwarden can encounter an absent data path. Validate the correct filesystem and mount before starting containers.

Evidence-based troubleshooting

Symptom Checks Safe response
Public URL is unavailable Test 127.0.0.1:8080, inspect the container, confirm our selected client is connected, and confirm the tunnel is started. Restore the first failed dependency. Do not create a replacement tunnel merely because the existing one stopped.
Certificate warning Inspect the hostname, validity period, chain, device clock, and exact client URL. Correct the certificate or URL. Never bypass the warning.
Administration login fails Confirm you entered the original password rather than the PHC hash. Inspect logs and persisted configuration. Correct the effective setting without printing the secret.
Argon2 setting is malformed Check that the entire value is single-quoted in .env and not duplicated in Compose. Regenerate the hash if necessary and avoid exposing resolved configuration output.
Client login works but sync fails Verify the self-hosted URL, certificate, access restrictions, outer authentication, and client compatibility. Test the web vault and remove incompatible outer challenges only after reviewing exposure.
Vault appears empty after reboot Stop Vaultwarden and use findmnt to verify the external filesystem. Compare the old and current data directories. Do not create records. Restore the correct mount or revert the Compose path.
Container cannot write to /data Inspect mount state, ownership, permissions, read-only flags, and logs. Compare with a known-good directory. Do not guess a user ID or grant world-writable permissions.
No recent backup Inspect the protected log, success marker, local encrypted files, SSH transfer, and remote destination. Correct the failed stage and run the script manually. Do not delete the last known-good copy.
Database integrity check fails Check source storage health, free space, and whether the snapshot used SQLite's backup mechanism. Preserve the failed set, create another snapshot, and retain the last verified off-host archive.
Attachments are absent after restore Confirm the matching attachments/ directory was included and restored to the active data path. Stop the target and restore attachments from the same backup set as the database.

Frequently asked questions

Is Vaultwarden fully compatible with every Bitwarden client?

No permanent universal guarantee should be assumed. Vaultwarden describes itself as a nearly complete implementation of the Bitwarden Client API. Test the exact browser, desktop, mobile, and feature combinations you require.

Can an administrator recover a forgotten master password?

Do not assume the operator can decrypt a user's vault or reveal a forgotten master password. Recovery depends on features and workflows configured before the loss. Preserve master passwords and two-factor recovery material outside the server.

Are Vaultwarden backups safe because vault records are encrypted?

No. Backups can contain encrypted records, metadata, attachments, Sends, signing keys, administration settings, and deployment secrets. Encrypt them, keep a protected off-host copy, separate the decryption key, and test restoration.

Do I need to own a domain?

No. A Localtonet HTTP tunnel can use a generated subdomain. Custom subdomains and custom domains may also be available depending on current product and plan behavior. Use the exact URL displayed by the dashboard.

Does creating a Localtonet tunnel make Vaultwarden permanently available?

No. Vaultwarden must be running, our selected client must remain connected, and the tunnel must be started. Startup persistence, storage readiness, relay availability, and plan-specific behavior also affect availability.

Is HTTPS used all the way into the Vaultwarden container?

Not in this design. The remote client uses HTTPS to the public endpoint. The Localtonet client then connects to Vaultwarden through local HTTP at 127.0.0.1:8080. Keeping both processes on the same trusted host confines that final connection to loopback.

Can Vaultwarden run on a Raspberry Pi?

It can run on compatible Raspberry Pi hardware when the current container image supports the architecture and the host uses a Docker-supported operating system. Verify architecture support and protect external storage against missing mounts.

How often should I test a restore?

Use a recurring schedule appropriate to the importance and rate of change of the vault. Repeat the test after material changes to Vaultwarden, storage, backup encryption, transfer automation, or the recovery host. Every test should use an isolated directory, container name, port, network, and data path.

Publish your loopback-only Vaultwarden through Localtonet

Install Vaultwarden, verify it locally, then create an HTTP tunnel to 127.0.0.1:8080. Keep the selected device connected, explicitly start the tunnel, verify startup after reboot, validate the public certificate, and complete an isolated restoration test before importing production credentials.

Get Started Free โ†’

Corrections & updates

Substantive changes approved by the Localtonet editorial team are listed transparently below.

Remove the outer article wrapper and move the hero to the beginning, followed immediately by the clickable guide card. Rebalance the guide links so they cover the most important sections without exceeding a useful navigation size. Link and align the Localtonet workflow with the current HTTP tunnel documentation, use first-party Localtonet language, and document or explicitly qualify client startup persistence after reboot. Replace the append-only DOMAIN command with an idempotent edit that prevents duplicate entries. Separate producti

Add the required lt-card guide navigation after the hero, give every linked h2 a unique lowercase fragment ID, shorten the hero title so it does not repeat the page H1, convert subsection headings to semantic h3 elements without lt-section-title, remove inline styles and unsupported classes, repair the malformed code block, and retain only approved lt-* components. Rebuild the comparison section around durable, sourced distinctions and remove or date-verify volatile pricing, RAM, container-count, audit, compatibility, and feature-enti

Localtonet is a secure multi-protocol tunneling and proxy platform designed to expose localhost, devices, private services, and AI agents to the public internet supporting HTTP/HTTPS tunnels, TCP/UDP forwarding, mobile proxy infrastructure, file server publishing, latency-optimized game connectivity, and developer-ready AI agent endpoint exposure from a single unified control plane.

support