<!-- skill: Bitwarden Self-Host | version: 1.0.0 | description: Vaultwarden compose, admin token, backups, 2FA and imports | category: System & Productivity -->
## Instructions
- Vaultwarden compose: `vaultwarden/server` image (not official Bitwarden — lighter), `/vw-data/` volume, `SIGNUPS_ALLOWED=false` after first account, `ADMIN_TOKEN` long random, reverse proxy (nginx/caddy) with TLS; never expose `:80` directly.
- Admin token: generate `openssl rand -base64 48`, store in `.env` (600) + password manager; admin panel at `/admin`; rotate yearly; disable signups immediately after creating your 2 accounts (personal + emergency).
- Backups: nightly `sqlite3 db.sqlite3 ".backup '/backup/vw-$(date +%F).db'"` + `tar -czf attachments.tgz ./attachments`, restic-vault to offsite, retention 30d; test restore to staging container quarterly.
- 2FA + passkeys: enforce 2FA (TOTP + FIDO2) on all users, `YUBICO_*` env if using YubiKey OTP, passkeys for web vault + mobile; print recovery codes to paper in safe, not in the vault itself.
- Import from browsers: Chrome/Edge `Passwords → Export .csv` → Bitwarden Tools → Import → delete CSV with `shred -u`; Firefox `about:logins` export likewise; dedupe + folder-sort (`Finance`, `Homelab`, `Shopping`) post-import.
- Organizations: family org for shared (`Home-WiFi`, `Streaming`), collections per trust level; never share personal login items, move copies into org; emergency access (trusted contact, 7-day wait).
- SMTP: set `SMTP_HOST/PORT/USER/PASSWORD/FROM` so invites + 2FA mails + emergency-access work; test with admin → users → invite; no SMTP = locked-out users on 2FA loss.
- Updates: `docker compose pull && docker compose up -d` monthly, backup first; pin `image: vaultwarden/server:1.32.0` not `:latest` for controlled rollbacks; `docker logs vw --tail 50` verify.
- Hardening: fail2ban on `/admin` + `/identity` 401s, geoblock non-home via pfSense, `ROCKET_PORT=80` internal only, data dir 700, `.env` 600; admin panel behind VPN/tailscale if possible.
- Mobile/desktop: official Bitwarden apps pointed at `https://vault.example.com`; biometric unlock + PIN, vault timeout 15 min; verify fingerprint phrase on first login (phishing guard).
- Pairs privacy-hardening: vault is the secret store that hardening assumes; backup plugin owns the offsite copy; rotate any credential that ever touched a plaintext CSV.
- Decommission browsers: turn off built-in password managers post-import (Chrome `Offer to save` off, Edge likewise) so no split-brain vaults.
## Examples
- Generate token: `openssl rand -base64 48` → paste to `.env` ADMIN_TOKEN.
- Backup now: `sqlite3 /vw-data/db.sqlite3 ".backup '/backup/vw-manual.db'" && tar -czf /backup/vw-files.tgz -C /vw-data attachments sends`.
- Invite family: Admin → Users → Invite → org collection assign → SMTP mail arrives.
- Import: Web vault → Tools → Import data → Chrome CSV → select file → Import → `shred -u ~/chrome-passwords.csv`.
## Workflows
1. Deploy: compose up → create account → signups off → admin token set → TLS verify.
2. Harden: 2FA all users → SMTP test → fail2ban → backups on → off-browser managers.
3. Import: export browser CSV → import → folder/org sort → shred CSV → audit weak/reused (Reports).
4. Operate: monthly update (backup first), quarterly restore test, yearly token rotation.
5. Emergency: recovery codes → emergency-access grant → admin DB restore if needed.
## Templates
```yaml
# docker-compose.yml — vaultwarden + caddy
services:
  vault:
    image: vaultwarden/server:1.32.0
    container_name: vw
    restart: unless-stopped
    env_file: .env
    volumes: [./vw-data:/data]
    ports: ["127.0.0.1:8080:80"]
  caddy:
    image: caddy:2-alpine
    restart: unless-stopped
    ports: ["80:80", "443:443"]
    volumes: [./Caddyfile:/etc/caddy/Caddyfile, ./caddy-data:/data, ./caddy-config:/config]
```
```bash
# .env (chmod 600)
DOMAIN=https://vault.example.com
ADMIN_TOKEN=PASTE-openssl-rand-base64-48
SIGNUPS_ALLOWED=false
INVITATIONS_ALLOWED=true
SMTP_HOST=mail.example.com
SMTP_PORT=587
SMTP_FROM=vault@example.com
SMTP_USERNAME=vault@example.com
SMTP_PASSWORD=app-password-here
SMTP_SECURITY=starttls
YUBICO_CLIENT_ID=changeme-or-blank
YUBICO_SECRET_KEY=changeme-or-blank
```
```caddy
# Caddyfile
vault.example.com {
  reverse_proxy 127.0.0.1:8080
}
```
```bash
#!/usr/bin/env bash
# backup-vw.sh (nightly cron)
set -euo pipefail
D=/backup/vw; mkdir -p "$D"
sqlite3 /opt/vw/vw-data/db.sqlite3 ".backup '$D/vw-$(date +%F).db'"
tar -czf "$D/vw-attach-$(date +%F).tgz" -C /opt/vw/vw-data attachments 2>/dev/null || true
ls -t "$D"/vw-*.db | tail -n +31 | xargs -r rm
```
## Tools
- Docker + compose, Vaultwarden 1.32+, Caddy/Nginx, sqlite3, official Bitwarden clients (web/mobile/desktop/CLI `bw`).
- `openssl rand`, `sqlite3 .backup`, restic-vault for offsite; Reports → Exposed/Reused/Inactive 2FA monthly.
## Advanced
- `bw` CLI: `bw login --sso`/`apikey`, `bw list items --search github | jq`, `bw get password github.com`; scripting rotate via API.
- Attachments: billed per-item in cloud, free local — cap 10MB; `sends` for one-shot secrets instead of email.
- SSO/OIDC: `SSO_*` env with Authelia/Keycloak for family SSO; keep one password-master fallback (SSO outage rule).
- Troubleshooting: 2FA mails never arrive → SMTP env + firewall 587; admin 401 → token mismatch (quote in .env, no `#`); attachments missing after restore → tar taken while container wrote (stop or sqlite-backup order); mobile sync stale → pull-to-refresh + server version check.
## Documentation
- Vaultwarden wiki + in-admin Help.
- https://github.com/dani-garcia/vaultwarden/wiki
- https://bitwarden.com/help/ + https://bitwarden.com/help/import-data/
