featured image

Taking Back the Keys: Escaping Subscription Fatigue with a Zero-Trust Password Vault

This tutorial guides you through setting up a self-hosted Vaultwarden instance on TrueNAS Scale, emphasizing data sovereignty and zero-trust security principles.

Published

Fri Nov 14 2025

Technologies Used

Vaultwarden TrueNAS Scale ZFS Tailscale Docker
Beginner 11 minutes

A password manager is the worst thing to host carelessly. It’s also one of the best arguments for self-hosting. Every month you pay for 1Password or LastPass, you’re paying a company to hold the master key to everything else you own. If their servers get breached — and they have been — your vault data is out there.

Vaultwarden is a community-written implementation of the Bitwarden server API, built in Rust, that runs comfortably on hardware that would struggle to run the official C# Bitwarden server. This tutorial walks through deploying it on TrueNAS Scale with persistent ZFS storage and access routed through a Tailscale mesh VPN, so the vault is never reachable from the public internet.

What You Need Before Starting

You should understand Docker volume mounts vs. bind mounts — specifically how Docker maps internal container paths to persistent host directories — because getting this wrong means your vault data disappears on container restart. You should also be comfortable with ZFS dataset hierarchies on TrueNAS, POSIX ACLs, and how Tailscale creates a peer-to-peer network using WireGuard underneath.

Environment requirements: TrueNAS Scale (ElectricEel or newer), a configured ZFS pool (e.g., tank), Tailscale already installed on the TrueNAS host, and the official Bitwarden browser extension on your client devices.

Why Vaultwarden Uses Less Than 150MB of RAM

The official Bitwarden server is written in C# and expects enterprise-grade infrastructure — often 4GB+ just to idle. Vaultwarden is written in Rust, which manages memory safely at compile-time with no garbage collector. Using the lightweight Rocket web framework, it can serve hundreds of concurrent WebSocket connections on a nine-year-old CPU using under 150MB of RAM.

ZFS’s Copy-on-Write model also matters here. Vaultwarden uses SQLite by default. On a standard filesystem like EXT4, database writes modify blocks in-place. If the system loses power mid-write, the database can corrupt. ZFS writes new data to a new block and only updates the filesystem pointer after the write is fully verified. Your password vault database can’t be caught in an incomplete write state.

Provisioning ZFS Datasets and the Container Stack

Create a parent dataset and two children in TrueNAS: vaultwarden/config (with Apps permissions) and vaultwarden/db (Generic permissions). Then the Docker Compose stack:

version: '3.8'

services:
  vaultwarden:
    image: vaultwarden/server:latest
    container_name: vaultwarden
    restart: unless-stopped
    ports:
      - "8080:80"
    volumes:
      - /mnt/tank/apps/vaultwarden/config:/data
      - /mnt/tank/apps/vaultwarden/db:/data/db
    environment:
      - ADMIN_TOKEN=$$argon2id$$v=19$$m=65536...[REDACTED]...
      - WEBSOCKET_ENABLED=true
      - DOMAIN=http://10.99.0.191:8080

The ADMIN_TOKEN environment variable grants access to the /admin panel. Set it during initial setup to configure the instance. The Argon2 hash format is required — Vaultwarden won’t accept a plaintext token.

Disabling the Admin Panel After Setup

Once you’ve done initial configuration through the admin panel (specifically: disable “Allow new signups” so random people can’t create accounts on your vault), you need to actually lock down the admin access. Just removing the ADMIN_TOKEN from your Docker Compose file is not sufficient. Vaultwarden caches settings from the admin panel in config.json, and those persist even if the environment variable disappears.

To fully disable the admin panel, you have to delete the token from the application’s config file directly:

cd /mnt/tank/apps/vaultwarden/config

nano config.json

# Inside config.json, locate and delete this line:
# "admin_token": "$argon2id$v=19$m=65...",

# Then restart the container to flush the cached config
docker restart vaultwarden

If you ever need admin access again later, inject a fresh Argon2 hash into config.json, restart, make your changes, and immediately delete the token again. Don’t leave it in config.json as a permanent fixture.

The reason this matters: Vaultwarden uses a settings hierarchy where config.json overrides environment variables. If an attacker discovers your port and you believe the admin panel is disabled because you removed the environment variable, but config.json still has a cached token, the admin panel is still accessible. The config.json must be explicitly cleared.

Why Tailscale Eliminates an Entire Class of Attacks

If Vaultwarden were exposed via port forwarding — say, port 443 open on your router — bots would hit the login endpoint around the clock. You’d need rate limiting, fail2ban, and hope that Vaultwarden’s login code has no zero-days.

With Tailscale, the server is simply invisible to the public internet. Any packet that doesn’t carry a valid WireGuard cryptographic signature gets dropped before the application ever sees it. There’s no login endpoint to hammer because the endpoint doesn’t exist from the perspective of the public internet. The application code never has to process malicious traffic.

Access works from anywhere on your Tailnet: your phone, your laptop, any device where you’ve installed Tailscale and authenticated. From the vault’s perspective, you’re always on the local network.

ZFS Snapshots as Disaster Recovery

Standard RAID is disk-level redundancy. It protects against a drive failing, not against you accidentally deleting your vault or a bad update corrupting it. ZFS snapshots handle the latter.

TrueNAS can schedule recursive, read-only snapshots of the vaultwarden dataset every 15 minutes. If anything goes wrong — bad container update, accidental deletion, corrupted database — you roll back the entire directory state to the last snapshot in a few seconds. The snapshot is consistent because ZFS’s Copy-on-Write model ensures every snapshot captures an atomic view of the filesystem, not a half-written state.

Configure this in TrueNAS under Data Protection > Periodic Snapshot Tasks. Target the vaultwarden dataset, set the retention policy to something reasonable (24 hourly, 7 daily), and let it run. You won’t think about it until you need it, and when you need it, you’ll be glad it was there.

We respect your privacy.

← View All Tutorials

Related Projects

    Ask me anything!