Self-Hosting Vaultwarden - Taking Back Password Management: Part 8 of “Building a Resilient Home Server” Series
The Survey
Bitwarden sent me a survey about privacy and AI. What password manager do you use? What email service? What data wouldn't you share with AI - passwords, financial info, medical records?
I filled it out. Checked every box for "keep AI away from this." Sent it in.
Then I checked my Vaultwarden dashboard. I'd set it up two days earlier. Been using it exclusively since - browser extension, mobile app, desktop client. Zero issues. My passwords syncing to my server, not theirs.
Talk about timing.
Bitwarden's cloud account is still there. Call it a failsafe while I validate the setup. But I haven't touched it since Vaultwarden went live.
Eventually, I'll empty the cloud account entirely. Keep it as a dead-man's switch if I ever need it. Everything will live on my server, with encrypted backups on my terms.
That's why we self-host. Not because I don't trust Bitwarden. Because when they send surveys asking about AI and my passwords, I want the option to walk away.
Why Vaultwarden?
Vaultwarden is a lightweight, unofficial Bitwarden server implementation written in Rust. It's fully compatible with all the official Bitwarden clients - browser extensions, mobile apps, desktop applications - but runs on a fraction of the resources.
The clients don't know the difference. They don't care. Same API, same encryption, same experience. Except the server is in my house, not someone else's datacenter.
I needed something that worked with the Bitwarden clients I was already using. I wasn't going to retrain muscle memory or deal with import/export headaches. Vaultwarden gave me that - same tools, different backend.
The Access Decision
Everything goes through Tailscale Funnel. No local HTTP access, no open ports, just HTTPS via Tailscale.
Why not local HTTP like Nextcloud? Because Bitwarden clients expect HTTPS. The browser extensions work with HTTP in development mode, but mobile apps get cranky without proper TLS. I could set up Caddy with Let's Encrypt, manage certificates, deal with DNS challenges. Or I could use Tailscale Funnel and get HTTPS with a valid certificate for free.
My phone needs remote access anyway. If I'm out and need a password, I'm going through Tailscale. Why maintain two different connection methods - one for local, one for remote - when Funnel handles both?
Sure, if my internet goes down, I can't access my passwords. But if my internet is down, I'm pretty screwed anyway. Can't get to most of the sites I'd need passwords for. My phone would fall back to Bitwarden's cloud (which is still active as a failsafe), and I'd deal with it.
Simpler to have one access method that works everywhere. Tailscale Funnel gives me that.
The Configuration
Vaultwarden is disabled by default in my config. I don't want services randomly starting while I'm still setting up secrets. The config is there, signups are locked down, but nothing runs until I explicitly enable it.
File: /etc/nixos/modules/services.nix
Nix
# Vaultwarden - Self-hosted password manager (Bitwarden-compatible)
# Disabled by default - requires manual setup
# See /etc/nixos/private-example/vaultwarden.env for configuration
services.vaultwarden = {
enable = false; # Set to true after configuring secrets
config = {
DOMAIN = "https://${secrets.tailscaleHostname}";
SIGNUPS_ALLOWED = false; # Disable after creating your account
INVITATIONS_ALLOWED = false;
SHOW_PASSWORD_HINT = false;
ROCKET_ADDRESS = "127.0.0.1";
ROCKET_PORT = 8222;
# Use SQLite (simple, reliable, easy to backup)
DATABASE_URL = "/var/local/vaultwarden/db.sqlite3";
# Store attachments locally
ATTACHMENTS_FOLDER = "/var/local/vaultwarden/attachments";
# Disable icon downloads (privacy)
DISABLE_ICON_DOWNLOAD = true;
# Admin panel (protect with strong token)
ADMIN_TOKEN = "@ADMIN_TOKEN@";
};
environmentFile = "/etc/nixos/private/vaultwarden.env";
backupDir = "/var/local/vaultwarden/backup";
};
# Create necessary directories
# File: /etc/nixos/modules/system.nix
systemd.tmpfiles.rules = [
"d /var/local/vaultwarden 0755 vaultwarden vaultwarden -"
"d /var/local/vaultwarden/backup 0755 vaultwarden vaultwarden -"
];
Port 8222 isn't in my firewall rules. It doesn't need to be. Everything goes through Tailscale Funnel, which handles the connection from the outside world to localhost:8222. The port is only accessible from the local machine.
File: /etc/nixos/private-example/vaultwarden.env
Bash
# Vaultwarden Environment Variables
# Copy this to /etc/nixos/private/vaultwarden.env and configure
# Admin panel token (generate with: openssl rand -base64 48)
ADMIN_TOKEN=CHANGE_THIS_TO_A_STRONG_RANDOM_TOKEN
# SMTP settings (optional - for email notifications)
# SMTP_HOST=smtp.example.com
# SMTP_FROM=vaultwarden@example.com
# SMTP_PORT=587
# SMTP_SECURITY=starttls
# SMTP_USERNAME=your-email@example.com
# SMTP_PASSWORD=your-app-password
I'm not using SMTP. Mostly didn't see much need, but hey, might change my mind later.
File: /etc/nixos/private-example/secrets.nix
Nix
{
# Tailscale hostname for Vaultwarden HTTPS access
# Get this from: tailscale status
# Example: nixos.tail123abc.ts.net
tailscaleHostname = "nixos.tailXXXXXX.ts.net";
# ... other secrets ...
}The Tailscale hostname goes in secrets because I don't want it exposed publicly. Sure, someone would need to be on my Tailscale network to access it anyway, but why make it easier? Security through obscurity isn't real security, but it doesn't hurt either.
Database Choice: SQLite
I went with SQLite. It's simple, it's reliable, and it's easy to backup. The entire database is one file. Stop the service, copy the file, restart the service. Done.
Could I use PostgreSQL? Sure. Vaultwarden supports it. But for a single-user password manager on a home server, SQLite is plenty. My database is under 1MB. Backups take milliseconds. If I ever need PostgreSQL's features (multiple concurrent writers, complex queries, replication), I can migrate. The path exists.
For now, SQLite does the job.
Monitoring
Vaultwarden doesn't expose Prometheus metrics, so I'm using blackbox probes. Same pattern as everything else - check if the service responds, alert if it doesn't.
File: /etc/nixos/modules/monitoring.nix
Nix
# Vaultwarden monitoring (blackbox probe)
{
job_name = "vaultwarden";
metrics_path = "/probe";
params = {
module = [ "http_2xx" ];
};
static_configs = [{
targets = [ "http://127.0.0.1:8222" ];
}];
relabel_configs = [
{
source_labels = [ "__address__" ];
target_label = "__param_target";
}
{
source_labels = [ "__param_target" ];
target_label = "instance";
}
{
target_label = "__address__";
replacement = "127.0.0.1:9115";
}
];
}
The probe hits localhost:8222 directly. It's checking if Vaultwarden is running and responding, not if Tailscale Funnel is working. If Funnel breaks, I'll notice when I try to log in. If Vaultwarden breaks, I want to know before I need it.
Alertmanager Rule:
Yaml
- alert: VaultwardenDown
expr: probe_success{job="vaultwarden"} == 0
for: 20m
labels:
severity: critical
annotations:
summary: "Vaultwarden is down"
description: "Vaultwarden has been unreachable for 20 minutes"
Twenty minutes before alerting. Vaultwarden is critical infrastructure, but I don't need a notification every time I reboot the server. If it's down for twenty minutes, something's actually wrong.
Setting It Up
I started by generating an admin token. Vaultwarden has an admin panel for managing users and settings, and it needs a strong token to access it.
Bash
nix-shell -p openssl --run "openssl rand -base64 48"
Copied the output, opened the environment file, pasted it in.
Bash
sudo micro /etc/nixos/private/vaultwarden.env
Bash
ADMIN_TOKEN='your_generated_token_here'
Made it secure:
Bash
sudo chmod 600 /etc/nixos/private/vaultwarden.env
Then I added my Tailscale hostname to secrets. Ran tailscale status, found my machine's hostname (something like nixos.tail123abc.ts.net), and added it to the secrets file.
Bash
sudo micro /etc/nixos/private/secrets.nix
Nix
tailscaleHostname = "nixos.tailXXXXXX.ts.net";
Next was Tailscale Funnel. This is where things got annoying.
Tailscale's Funnel setup is a two-step process. First, you enable Funnel as a feature for your entire network. Then you enable it on the specific node that'll use it.
There's a Funnel section in Settings. Click "Manage" and it takes you to... Access controls → Node attributes. Which is buried under a sales pitch for their paid plans. The visual editor is a maze of tabs and nested settings. I gave up and used the JSON editor instead.
Access controls → JSON editor. There's a sidebar with a "Funnel" section that'll insert the config for you. Click it, save the ACL. Done.
Then I ran the CLI command:
Bash
sudo tailscale funnel --bg --https=443 http://127.0.0.1:8222
It gave me a URL. Copied it, pasted it into my browser, clicked "Enable Funnel."
Why can't the CLI just enable it directly? Probably security - they want explicit admin approval before a node accepts public internet traffic. Makes sense in theory. Still annoying in practice.
Why does Settings → Funnel → Manage take you to a completely different section instead of just... managing Funnel? No idea. Tailscale's UI is powerful, but it's not intuitive.
With Funnel enabled, I went back to the NixOS config and enabled Vaultwarden:
Bash
sudo micro /etc/nixos/modules/services.nix
Changed services.vaultwarden.enable = false; to true.
I have nh installed on my system - it's a nice wrapper around nixos-rebuild with better output and some quality-of-life features. But for consistency throughout this blog series, I'm showing the standard commands that work on any NixOS system:
Bash
sudo nixos-rebuild switch
Vaultwarden started. I accessed it at https://nixos.tailXXXXXX.ts.net (my actual Tailscale hostname), clicked "Create Account", and set up my master password.
I used a strong master password. This is the password manager. Everything else depends on it being secure. I'm not going to document what makes a strong password here - if you're self-hosting a password manager, you already know.
After creating the account, I immediately enabled 2FA. Went to Settings → Security → Two-step Login, enabled authenticator app, scanned the QR code with my phone. Saved the recovery code somewhere safe (not in Vaultwarden - that would be stupid).
I printed the recovery code and put it in my fireproof safe. Old school, but it works.
Then I verified signups were disabled. Opened the config, checked SIGNUPS_ALLOWED = false. It was already set (I'd configured it that way from the start), but I double-checked anyway. Rebuilt just to be sure:
Bash
sudo nixos-rebuild switch
Now no one else can create accounts. It's just me.
Migrating From Bitwarden Cloud
Before I connected any clients, I exported my data from Bitwarden's cloud. Went to the web vault, Settings → Export Vault, chose "Encrypted .json" format. Downloaded it.
Then I imported it into Vaultwarden. Logged into the Vaultwarden web vault, Settings → Import Data, selected the encrypted export file. Everything came over - passwords, notes, secure notes, identities, cards. All of it.
Only after the import did I point my clients at the new server. This way, if something went wrong during the import, I still had the cloud vault as a fallback.
Connecting The Clients
I pointed my Bitwarden browser extension at the new server first. Settings → Self-hosted → Server URL → https://nixos.tailXXXXXX.ts.net. Logged in. Everything synced.
Did the same on my phone. Bitwarden app → Settings → Self-hosted → https://nixos.tailXXXXXX.ts.net. Logged in. Synced.
Desktop app. Same process.
The clients don't know they're talking to Vaultwarden instead of Bitwarden's cloud. They don't care. Same API, same encryption, same experience.
I've been using it exclusively for two days. Zero issues. Passwords sync across devices. Autofill works. Mobile app works. It's boring. Which is exactly what I want from a password manager.
My Bitwarden cloud account is still active. I'm not deleting it yet. Call it cautious. Vaultwarden works great, but I want more time to trust it. If something catastrophic happens - server dies, database corrupts, I screw up a config - I've got a fallback.
Eventually, I'll empty the cloud account entirely. Keep it as a dead-man's switch if I ever need it. Everything will live on my server, with encrypted backups on my terms. But for now, it's there. Insurance.
Backups
Vaultwarden stores everything in a few key locations:
-
/var/local/vaultwarden/db.sqlite3- The database (all your passwords) -
/var/local/vaultwarden/attachments/- File attachments -
/var/local/vaultwarden/config.json- Configuration -
/var/local/vaultwarden/rsa_key.*- Encryption keys
I'll cover automated backups in a future post, but the basics: stop Vaultwarden, copy everything, restart Vaultwarden. SQLite doesn't like concurrent access, so you stop the service before backing up the database.
Quick manual backup:
Bash
sudo systemctl stop vaultwarden
sudo cp -r /var/local/vaultwarden /path/to/backup/vaultwarden-$(date +%Y%m%d)
sudo systemctl start vaultwarden
The database is small (mine is under 1MB), so backups are fast. Stop, copy, start. Done in seconds.
Security
I disabled signups after creating my account. Enabled 2FA immediately. Used a strong admin token (48 bytes of random data). Access is only via Tailscale Funnel - authenticated, encrypted, no direct internet exposure.
Vaultwarden is technically on the public internet (that's what Funnel does), but:
- Tailscale handles authentication (only my devices can connect)
- The connection is encrypted (TLS via Tailscale)
- Vaultwarden itself requires authentication (master password + 2FA)
- Signups are disabled (can't create new accounts)
- Admin panel requires a strong token
Defense in depth. Multiple layers. If one fails, the others hold.
I'm not worried about someone brute-forcing my master password. I'm not worried about someone creating an account (signups are disabled). I'm not worried about someone accessing the admin panel (strong token, only accessible via Tailscale).
What I am worried about: Losing the data. That's why backups matter. That's why I'm keeping the Bitwarden cloud account active for now. That's why I'll eventually move to encrypted backups on my own infrastructure.
Security isn't just about keeping people out. It's about making sure you don't lose what you're protecting.
Why This Matters
The survey asked what data I wouldn't share with AI. Passwords were on the list. Financial info. Medical records. Personal information.
I checked all the boxes. Sent it in. Hoping they'd get the message.
But the real answer is: I don't want to have to trust them with that decision. I don't want to hope they make the right call when they're deciding what features to build or what data to process or what third parties to partner with.
Self-hosting isn't about being paranoid. It's about having options. It's about knowing that if Bitwarden Inc. changes direction - new CEO, new investors, new "AI-powered insights" - I can walk away.
Vaultwarden gives me that option. My passwords are on my server. My data is under my control. If Bitwarden announces something I hate, I'm already gone. I just delete the cloud account and move on.
That's worth the effort.
What's Next?
The monitoring stack is complete. The core services are running. The disaster recovery ISO works. Vaultwarden is live.
Next up: Automated backups. Because all of this is pointless if I lose data.
Part 9: Automated Backups with Restic - Coming soon.
The Config Files
All configuration files from this post are in my NixOS configuration repository.
Key files:
-
modules/services.nix- Vaultwarden service configuration -
modules/system.nix- Directory creation rules -
modules/monitoring.nix- Monitoring integration -
private-example/vaultwarden.env- Environment template -
private-example/secrets.nix- Secrets template
Remember: The private-example/ directory contains templates. Copy them to private/ and fill in your actual secrets.
Main Server (nixos): Codeberg
Second Server (nixos2): Codeberg
ISO can be gotten here.
Have you taken back your passwords? Let me know! Find me at @ppb1701@ppb.social on Mastodon.
Next: Part 9: Automated Backups