Setting Up a Self-Hosted Password Manager with Bitwarden
I stopped trusting cloud password managers about two years ago. Not because Bitwarden's cloud is insecure—it's actually excellent—but because I wanted total control over my vault. Now I run Bitwarden entirely on my own infrastructure, and I'm going to walk you through exactly how to do it. By the end of this guide, you'll have a fully functional, encrypted password manager that only you can access.
Why Self-Host Your Password Manager?
When you self-host Bitwarden, your encrypted vault lives on hardware you control. Your master password never leaves your device. There's no middle-man, no compliance audits to worry about (unless you're in heavily regulated work), and most importantly, you own your data completely. I prefer this because it aligns with my philosophy: important stuff stays close to home.
The trade-off? You're responsible for backups, SSL certificates, and keeping the server patched. But if you're already running a VPS for other services, the overhead is minimal. And if you don't have a VPS yet, a basic 1-core, 1GB RAM instance from RackNerd runs about $40 per year—easily worth it for password management alone.
What You'll Need
For this setup, you need:
- A VPS or home server running Linux (Ubuntu 20.04 LTS or newer recommended)
- A domain name you control
- Docker and Docker Compose installed
- Basic SSH access to your server
- About 30 minutes and a cup of coffee
If you're shopping for a VPS, I've been using RackNerd's offerings for small projects like this. Their new-year promotions typically offer instances that handle Bitwarden with room to spare, and pricing is genuinely competitive for annual commitments.
Installing Docker and Docker Compose
Log into your VPS and start fresh. I always install Docker's official repository to get the latest stable version.
#!/bin/bash
# Update system
sudo apt update && sudo apt upgrade -y
# Install Docker prerequisites
sudo apt install -y apt-transport-https ca-certificates curl software-properties-common
# Add Docker's official GPG key
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
# Add Docker repository
echo "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
# Install Docker and Docker Compose
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
# Start Docker daemon
sudo systemctl start docker
sudo systemctl enable docker
# Add your user to the docker group (optional but convenient)
sudo usermod -aG docker $USER
newgrp docker
Verify everything works:
docker --version
docker compose version
Setting Up Bitwarden with Docker Compose
Now I'll create the Bitwarden stack. Create a directory for your project and set up the compose file:
mkdir -p ~/bitwarden && cd ~/bitwarden
nano docker-compose.yml
Paste this configuration:
version: '3.8'
services:
bitwarden:
image: bitwardenrs/server:latest
container_name: bitwarden
restart: always
environment:
DOMAIN: https://vault.example.com
SIGNUPS_ALLOWED: "false"
INVITATIONS_ORG_ALLOW_USER: "true"
SHOW_PASSWORD_HINT: "false"
LOG_FILE: /data/bitwarden.log
LOG_LEVEL: info
EXTENDED_LOGGING: "true"
EXTENDED_LOGGING_FILE: /data/extended.log
ADMIN_TOKEN: ${ADMIN_TOKEN}
WEBSOCKET_ENABLED: "true"
ROCKET_PORT: 80
volumes:
- ./bw-data:/data
ports:
- "127.0.0.1:8000:80"
networks:
- bitwarden_net
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost/alive"]
interval: 30s
timeout: 5s
retries: 3
caddy:
image: caddy:latest
container_name: caddy-bitwarden
restart: always
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- ./caddy-data:/data
- ./caddy-config:/config
networks:
- bitwarden_net
environment:
DOMAIN: vault.example.com
networks:
bitwarden_net:
driver: bridge
Create the Caddyfile for SSL termination:
nano Caddyfile
Add this:
vault.example.com {
reverse_proxy http://bitwarden:80
encode gzip
header / {
X-Frame-Options "SAMEORIGIN"
X-Content-Type-Options "nosniff"
X-XSS-Protection "1; mode=block"
Referrer-Policy "same-origin"
}
}
Replace vault.example.com with your actual domain. Caddy will automatically provision a Let's Encrypt certificate on first run.
Generating the Admin Token
You need an admin token to access the admin panel. Generate one before starting:
nano .env
Add this line (generate a strong random string):
ADMIN_TOKEN=your_random_admin_token_here_min_32_chars
To generate a token, I use:
openssl rand -base64 32
Starting the Services
Pull the images and start everything:
docker compose pull
docker compose up -d
Check the logs to ensure everything initialized correctly:
docker compose logs -f bitwarden
Wait 30-60 seconds for the database to initialize. You should see messages about listening on port 80 and the database being created. When you see clean logs with no errors, the container is ready.
Configuring Bitwarden
Visit https://vault.example.com/admin in your browser. Enter your admin token. From here, you can:
- Disable signups — Already set in the compose file, but double-check here
- Configure SMTP — So you can send invitations to other users (optional)
- Set organizational policies — Enforce strong passwords, two-factor auth, etc.
Create your main account at https://vault.example.com/#/register. Use a strong master password—this is the key to everything. Then immediately disable further signups in the admin panel by removing the admin token.
Securing Your Instance
Self-hosting means you're responsible for security. Here's my checklist:
Firewall rules: Only open ports 80 and 443 to the world. SSH access should be restricted to your IP or a Tailscale network.
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 22/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
Disable the admin panel after setup: Once configured, remove or set a random admin token. You can always restart with a new one if needed.
Automate updates: Use Watchtower to keep images patched. Add this service to your compose file:
watchtower:
image: containrrr/watchtower:latest
container_name: watchtower
restart: always
volumes:
- /var/run/docker.sock:/var/run/docker.sock
command: --interval 86400 --cleanup
networks:
- bitwarden_net
Set up backups: Back up your bw-data directory daily. I use a simple cron job that compresses and ships it to cold storage.
#!/bin/bash
BACKUP_DIR="/backups/bitwarden"
DATA_DIR="$HOME/bitwarden/bw-data"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
mkdir -p $BACKUP_DIR
tar -czf $BACKUP_DIR/bitwarden_$TIMESTAMP.tar.gz $DATA_DIR
find $BACKUP_DIR -name "*.tar.gz" -mtime +30 -delete
Accessing Bitwarden Remotely
Your Bitwarden instance is now live on the public internet. Use the web vault at https://vault.example.com, or install the official browser extensions and mobile apps. Just log in with your account credentials.
For additional security, I recommend using a Tailscale network to restrict access. Change your Caddyfile to only accept connections from your Tailscale subnet, or put the entire instance behind Authelia for zero-trust access.
Migrating from Cloud Services
If you're coming from Bitwarden Cloud, the migration is seamless. Export your vault (Settings → Tools → Export Vault) as encrypted JSON, import it into your self-hosted instance, and you're done. All passwords transfer over.
For other password managers (LastPass, 1Password, KeePass), export to CSV or JSON, and Bitwarden can import those formats too.
Next Steps
You've now got a fully encrypted, self-hosted password manager. Next, consider:
- Enable passwordless authentication with passkeys (FIDO2)
- Set up organization accounts if you're managing passwords for multiple people
- Implement automated backups to S3 or a secondary VPS
- Integrate Bitwarden with other services via the API (advanced)
Questions? Self-hosting Bitwarden is rock-solid—the official documentation is thorough, and the community is helpful. Drop a comment below if you hit any snags.
Discussion