Vaultwarden Bitwarden Self-Host: Team Vaults, Organizations, and Security Hardening for Production
Getting Vaultwarden running is the easy part. The real value comes from configuring it properly for a team — organizations with scoped access, collections that map to your actual workflows, enforced two-factor authentication, SMTP for user lifecycle management, and a hardened server that doesn't leak credentials through misconfiguration. This guide covers production Vaultwarden Bitwarden self-host configuration for teams who have moved past the initial deployment and need to run it seriously.
If you haven't deployed Vaultwarden yet, start with our Vaultwarden deployment guide which covers Docker Compose setup, Nginx HTTPS configuration, and first-time admin panel access. This guide picks up from a running instance.
Prerequisites
- A running Vaultwarden instance with HTTPS configured (see our deployment guide)
- Admin panel access at
https://vault.yourdomain.com/admin - SMTP configured and working — user invitations, 2FA recovery, and emergency access all require email
- The official Bitwarden clients installed on at least one device for testing
- Docker and Docker Compose on the host for configuration changes
Verify your instance is healthy before making changes:
# Check Vaultwarden is running and responsive
curl -s https://vault.yourdomain.com/api/config | jq '.version'
# Test SMTP is working (from admin panel):
# Admin → SMTP Email Settings → Send test email
# Or check from the command line:
docker logs vaultwarden --tail 20 | grep -i smtp
# Verify admin panel is accessible
curl -I https://vault.yourdomain.com/admin
# Should return 200
Understanding Vaultwarden's Team Structure
Before configuring anything for your team, it helps to understand how Bitwarden's access model maps to real workflows. Getting this right from the start saves you painful reorganization later.
Organizations, Collections, and Groups
- Organization — a shared account that owns credentials and manages members. Think of it as your company's vault. One organization per company is usually right.
- Collection — a named group of credentials within an organization. Map these to teams, projects, or access levels: Engineering, DevOps, AWS Production, Shared Accounts.
- Groups — sets of users that can be assigned to collections with specific permissions. Groups let you manage collection access at scale rather than per-user. Groups are available in Vaultwarden free; the official server charges for this.
- Member roles: Owner, Admin, Manager, User, Custom. Owners and Admins can manage members and collections. Managers control specific collections. Users just access.
Recommended Structure for an Engineering Team
# Suggested organization structure:
Organization: Acme Corp
├── Collections:
│ ├── Shared — All Staff (all users, read-only for most)
│ ├── Engineering — General (engineering group, read/write)
│ ├── DevOps — Infrastructure (devops group, read/write)
│ ├── DevOps — Production Secrets (senior devops only, read/write)
│ ├── Finance — Accounts (finance group only)
│ └── Executive — Sensitive (owners only)
├── Groups:
│ ├── Engineering (all devs)
│ ├── DevOps (ops team)
│ ├── Finance (finance team)
│ └── Admins (IT/security team)
└── Members: all staff accounts
Setting Up Organizations and Collections
Creating the Organization
Log into the Vaultwarden web vault as your admin user. Go to New Organization from the left sidebar. Set the organization name, billing email (for notifications), and plan. In Vaultwarden, all features are available regardless of plan selection — choose any and proceed.
Creating Collections
Inside the organization, go to Manage → Collections → New Collection. Create one collection per access scope. Start conservative — you can always merge collections later but splitting an overcrowded one is painful.
For each collection, set the external ID to a machine-readable slug like devops-production. This matters when you're syncing access via the API or LDAP later.
Creating Groups and Assigning Collections
Go to Manage → Groups → New Group. Create groups matching your team structure. For each group:
- Set the group name and optionally an external ID
- Go to Collections tab — assign which collections this group can access
- Set permission level per collection: Can View, Can Edit, or Can Manage
The separation between Can View and Can Edit is important for sensitive collections — junior team members can use credentials without being able to change or delete them.
Inviting Team Members
# Bulk invite via Vaultwarden admin API
# First get an admin session (POST to /admin with your token)
# Invite a user to the organization via Bitwarden API
# (requires an org admin API key from Organization Settings → API Key)
curl -X POST https://vault.yourdomain.com/api/organizations/YOUR_ORG_ID/users/invite \
-H 'Content-Type: application/json' \
-H "Authorization: Bearer $ORG_ACCESS_TOKEN" \
-d '{
"emails": [
"[email protected]",
"[email protected]",
"[email protected]"
],
"type": 2,
"collections": [],
"groups": [],
"accessAll": false
}'
# After inviting, assign users to groups:
# Organization → Manage → Groups → [Group Name] → Members → Add Member
Confirming New Members
When a new member accepts their invitation, they appear in the organization with status Accepted. An org admin must Confirm them — this is a cryptographic step that shares the organization's encryption key with the new member's public key. Without confirmation, they can log in but can't see organization credentials. Go to Manage → People and confirm each new member.
Security Hardening
Enforce Two-Factor Authentication
Two-factor authentication must be non-optional for a password manager holding your team's infrastructure credentials. Configure it at the organization level so members without 2FA enabled are blocked from accessing org credentials:
# In Vaultwarden admin panel (https://vault.yourdomain.com/admin):
# General Settings → Require Two-Factor Authentication → Enable
# Or via docker-compose.yml environment:
- REQUIRE_DEVICE_EMAIL=true # Require email verification for new devices
# Individual users set up 2FA under:
# Account Settings → Security → Two-step Login
# Recommend TOTP (Authenticator app) as minimum
# Optionally enable FIDO2/WebAuthn for hardware keys
# Organization policy (in Organization Settings → Policies):
# Enable "Two-step Login" policy
# Members without 2FA enabled cannot access org vault items
Restrict Admin Panel Access
The /admin panel uses a static token — if that token is compromised, an attacker has full server access. Restrict it at the Nginx level to your IP or VPN subnet:
# Add to your Nginx server block for vault.yourdomain.com:
location /admin {
# Allow only your static IP and VPN subnet
allow 203.0.113.0; # Your office/home IP
allow 10.8.0.0/24; # Your VPN subnet
deny all;
proxy_pass http://localhost:8080;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# Reload Nginx after adding:
sudo nginx -t && sudo systemctl reload nginx
Harden the Admin Token
Vaultwarden now supports bcrypt-hashed admin tokens, which are safer than plain tokens — even if your config file is read, the token can't be used directly. Generate and apply a hashed token:
# Generate a bcrypt hash of your admin token
# First, have a strong plain token ready:
PLAIN_TOKEN=$(openssl rand -base64 48)
echo "Your plain token (save this): $PLAIN_TOKEN"
# Hash it with bcrypt cost factor 12:
pip3 install bcrypt
python3 -c "
import bcrypt
token = b'$PLAIN_TOKEN'
hashed = bcrypt.hashpw(token, bcrypt.gensalt(rounds=12))
print(hashed.decode())
"
# The output looks like: $2b$12$...
# Escape $ signs for Docker Compose by doubling them:
# $2b$12$abc... becomes $$2b$$12$$abc...
# Update .env:
# ADMIN_TOKEN=$$2b$$12$$your-hashed-token-here
# When logging into /admin, use the PLAIN token
# Vaultwarden verifies it against the hash
docker compose up -d --force-recreate vaultwarden
Disable Unused Features
# Add to docker-compose.yml environment to lock down what you don't need:
# Disable open signups — only invited users can register
- SIGNUPS_ALLOWED=false
# Disable Bitwarden Send if you don't want encrypted file sharing
- SENDS_ALLOWED=false
# Disable emergency access if your organization doesn't need it
# (reduces attack surface)
- EMERGENCY_ACCESS_ALLOWED=false
# Restrict signups to your company email domain
# (belt-and-suspenders if SIGNUPS_ALLOWED is somehow re-enabled)
- SIGNUPS_DOMAINS_WHITELIST=company.com
# Set a token expiry for web vault sessions (seconds)
# Default is no expiry — 86400 = 24 hours
- WEB_VAULT_ENABLED=true
# Restart after changes:
docker compose up -d --force-recreate vaultwarden
Managing Credentials at Scale
Using the Bitwarden CLI for Automation
The Bitwarden CLI is the right way to manage credentials programmatically — scripting secret rotation, injecting secrets into CI/CD pipelines, and bulk-importing credentials. Configure it to use your self-hosted instance:
npm install -g @bitwarden/cli
# Point at your instance
bw config server https://vault.yourdomain.com
# Log in and unlock
bw login [email protected]
export BW_SESSION=$(bw unlock --raw)
# Sync to get latest state
bw sync --session $BW_SESSION
# Get a credential by name (for use in scripts)
DB_PASS=$(bw get password "Production PostgreSQL" --session $BW_SESSION)
# Get an item's full JSON
bw get item "AWS Production Root" --session $BW_SESSION | jq .
# List all items in a collection
COLLECTION_ID=$(bw list collections --session $BW_SESSION | \
jq -r '.[] | select(.name=="DevOps — Production Secrets") | .id')
bw list items --collectionid $COLLECTION_ID --session $BW_SESSION | \
jq '[.[] | {name: .name, username: .login.username}]'
# Lock when done
bw lock
Bulk Importing Credentials
If you're migrating from another password manager or importing a CSV export, Bitwarden's import format handles bulk operations:
# Export from LastPass, 1Password, Dashlane, or any CSV source
# Then import via the web vault:
# Tools → Import Data → Choose format → Upload file
# Or import via CLI (JSON format):
# First export existing vault to see the format:
bw export --format json --session $BW_SESSION > vault-export.json
# Import a JSON file:
bw import bitwardenjson vault-export.json --session $BW_SESSION
# Import to a specific organization:
bw import bitwardenjson org-export.json \
--organizationid YOUR_ORG_ID \
--session $BW_SESSION
# Supported import formats include:
# lastpasscsv, 1password1pux, dashlanejson,
# keepassxml, chromecsv, and many more
Secret Rotation Workflow
Vaultwarden doesn't automate secret rotation — but the CLI makes it easy to build rotation workflows that update credentials in Vaultwarden after rotating them in the actual service:
#!/bin/bash
# rotate-db-password.sh
# Rotates a PostgreSQL password and updates Vaultwarden
set -euo pipefail
# Generate new password
NEW_PASS=$(openssl rand -base64 32)
# Rotate in PostgreSQL
psql -h $DB_HOST -U admin -c \
"ALTER USER appuser WITH PASSWORD '$NEW_PASS';"
# Update in Vaultwarden via CLI
export BW_SESSION=$(bw unlock --raw)
bw sync --session $BW_SESSION
# Get the item ID
ITEM_ID=$(bw get item "Production PostgreSQL" --session $BW_SESSION | jq -r '.id')
# Get current item JSON and update password
bw get item $ITEM_ID --session $BW_SESSION | \
jq --arg pass "$NEW_PASS" '.login.password = $pass' | \
bw encode | \
bw edit item $ITEM_ID --session $BW_SESSION
bw lock
echo "Password rotated and updated in vault: $(date)"
Backup, Recovery, and Disaster Planning
Automated Daily Backups
Your Vaultwarden database contains every credential your team depends on. Treat backup failure as a P0 incident waiting to happen:
#!/bin/bash
# /opt/scripts/backup-vaultwarden.sh
set -euo pipefail
BACKUP_DIR="/opt/backups/vaultwarden"
DATE=$(date +%Y-%m-%d-%H%M)
S3_BUCKET="s3://your-backup-bucket/vaultwarden"
mkdir -p $BACKUP_DIR
# SQLite backup (atomic — consistent snapshot)
docker exec vaultwarden sqlite3 /data/db.sqlite3 \
".backup '/data/backup-${DATE}.sqlite'"
# Copy backup out of container
docker cp vaultwarden:/data/backup-${DATE}.sqlite \
${BACKUP_DIR}/db-${DATE}.sqlite
# Also backup attachments and config
docker run --rm \
-v vaultwarden_vaultwarden_data:/data:ro \
-v ${BACKUP_DIR}:/backup \
alpine tar czf /backup/full-${DATE}.tar.gz /data
# Encrypt before offsite storage
gpg --symmetric --cipher-algo AES256 \
--batch --passphrase-file /etc/backup-key \
${BACKUP_DIR}/full-${DATE}.tar.gz
# Upload to S3 (or MinIO)
aws s3 cp ${BACKUP_DIR}/full-${DATE}.tar.gz.gpg ${S3_BUCKET}/
# Clean up local backups older than 7 days
find ${BACKUP_DIR} -mtime +7 -delete
echo "Backup completed: ${DATE}"
# Add to crontab: crontab -e
# Run backup daily at 2am and 2pm
0 2,14 * * * /opt/scripts/backup-vaultwarden.sh >> /var/log/vaultwarden-backup.log 2>&1
Testing Your Backup
A backup you've never tested is not a backup. Restore to a test environment quarterly:
# Restore test procedure:
# 1. Decrypt the backup
gpg --decrypt --passphrase-file /etc/backup-key \
full-2026-04-06-0200.tar.gz.gpg > full-restored.tar.gz
# 2. Extract to a test directory
mkdir -p /tmp/vw-test-restore
tar xzf full-restored.tar.gz -C /tmp/vw-test-restore
# 3. Start a test Vaultwarden instance pointing at the restored data
docker run -d \
--name vw-test \
-p 8181:80 \
-v /tmp/vw-test-restore/data:/data \
-e ADMIN_TOKEN=test-only-token \
-e DOMAIN=http://localhost:8181 \
vaultwarden/server:latest
# 4. Verify you can log in and see credentials
curl http://localhost:8181/api/config | jq .version
# 5. Clean up
docker stop vw-test && docker rm vw-test
rm -rf /tmp/vw-test-restore
Tips, Gotchas, and Troubleshooting
New Members Can't See Organization Credentials After Accepting
This is almost always the confirmation step being skipped. Org admins must explicitly confirm each accepted member before the cryptographic key exchange completes:
# Check member status via API
curl https://vault.yourdomain.com/api/organizations/YOUR_ORG_ID/users \
-H "Authorization: Bearer $ORG_ACCESS_TOKEN" | \
jq '[.data[] | {name: .name, email: .email, status: .status}]'
# Status values:
# 0 = Invited (not yet accepted)
# 1 = Accepted (needs confirmation)
# 2 = Confirmed (fully active)
# Confirm all accepted members:
# Organization → Manage → People → filter by "Accepted" → Confirm
# Auto-confirm via API (useful for automation):
curl -X POST \
https://vault.yourdomain.com/api/organizations/ORG_ID/users/USER_ID/confirm \
-H "Authorization: Bearer $ORG_ACCESS_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"key": "ENCRYPTED_SHARE_KEY"}'
Users Locked Out After Losing 2FA Device
If a team member loses their 2FA device and didn't set up recovery codes:
# As admin, disable 2FA for the locked-out user:
# Admin Panel → Users → [User] → Remove 2FA
# Or via the admin API:
curl -X DELETE \
https://vault.yourdomain.com/admin/users/USER_UUID/2fa \
-H 'Content-Type: application/json' \
-b "admin_token=YOUR_ADMIN_TOKEN"
# The user can then log in and re-enroll a new 2FA device
# Make sure all users save their 2FA recovery codes
# and store them somewhere safe (not in Vaultwarden itself)
Updating Vaultwarden Safely
# Always back up first
docker exec vaultwarden sqlite3 /data/db.sqlite3 \
".backup '/data/pre-update-backup.sqlite'"
docker cp vaultwarden:/data/pre-update-backup.sqlite \
~/vw-backup-$(date +%Y-%m-%d).sqlite
# Check release notes for breaking changes:
# https://github.com/dani-garcia/vaultwarden/releases
# Pull and restart
docker compose pull
docker compose up -d
# Verify health after update
curl -s https://vault.yourdomain.com/api/config | jq '.version'
docker logs vaultwarden --tail 10
Pro Tips
- Use named collections for CI/CD secrets — create a collection called CI/CD Tokens and store all service account tokens and deploy keys there. The Bitwarden CLI can fetch them at pipeline runtime, eliminating hardcoded secrets in your CI environment variables.
- Set up emergency access between trusted admins — Vaultwarden supports Bitwarden's Emergency Access feature, where one user can designate another as an emergency contact who can request access to their vault after a waiting period. Set this up between your two most senior admins so there's always a recovery path.
- Audit vault access regularly — check the admin panel's event log monthly. Look for unusual login locations, failed 2FA attempts, and bulk credential accesses that might indicate a compromised account.
- Don't store Vaultwarden credentials in Vaultwarden — your admin token, SMTP password, and backup encryption key need to exist somewhere you can access if the vault is down. A physical safe or an offline encrypted file is appropriate for these.
- Monitor Vaultwarden's HTTPS cert expiry — an expired cert on your vault server locks out every team member simultaneously. Set up a cert expiry monitor in Uptime Kuma to alert 30 days before expiry.
Wrapping Up
A properly configured Vaultwarden Bitwarden self-host for a team is more than a deployment — it's an access control system for your most sensitive credentials. Organizations and collections give you the structure. Enforced 2FA and IP-restricted admin access give you the security posture. Automated backups with tested restores give you the resilience. And the Bitwarden CLI gives your engineering team programmatic access to secrets without anyone ever seeing a plaintext credential in a config file.
If you're still setting up your initial deployment, start with our Vaultwarden deployment guide to get Docker, Nginx, and HTTPS configured first. Then come back here to layer in the team features and hardening that make it production-ready.
Need a Secure Credential Management System for Your Team?
Deploying Vaultwarden is one piece of a complete secrets management strategy. If you need help designing credential workflows, integrating vault access into CI/CD pipelines, setting up LDAP sync, or building the surrounding security infrastructure — the sysbrix team can design and implement it end to end.
Talk to Us →