# Runbook: Vaultwarden health (us2) Target: [us2.wsvc.info](../hosts/us2.wsvc.info.md) Path: `/opt/vaultwarden` URL: https://auth.wsvc.info/ Upstream: [docs/vaultwarden-upstream.md](../docs/vaultwarden-upstream.md) Prefer the Ansible health report (`ansible/playbooks/health-report.yml` with `--limit vaultwarden`) for the routine check. The SSH commands below are focused diagnostics for an already identified problem. ## Focused SSH diagnostics Use these only after the Ansible health report needs investigation. ### 1. Containers ```bash ssh -4 windy@us2.wsvc.info 'cd /opt/vaultwarden && docker compose ps -a' ``` Expect `vaultwarden` + `vw-db` **Up (healthy)**. `pgweb` should be stopped unless started with `--profile debug`. ### 2. Live DB is Postgres ```bash ssh -4 windy@us2.wsvc.info 'cd /opt/vaultwarden && docker compose exec -T vaultwarden sh -c "printenv DATABASE_URL" | sed -E "s#://[^@]+@#://REDACTED@#"' ssh -4 windy@us2.wsvc.info 'cd /opt/vaultwarden && docker compose exec -T pg psql -U vaultwarden -d vaultwarden -c "SELECT COUNT(*) AS users FROM users; SELECT COUNT(*) AS ciphers FROM ciphers;"' ``` ### 3. Effective config (config.json wins) ```bash ssh -4 windy@us2.wsvc.info 'cd /opt/vaultwarden && docker compose exec -T vaultwarden cat /data/config.json' \ | python3 -c "import sys,json;d=json.load(sys.stdin);print({k:d.get(k) for k in ['domain','smtp_host','smtp_port','smtp_security','smtp_username','smtp_from','ip_header','signups_allowed']});print('smtp_password_set',bool(d.get('smtp_password')))" ``` Expect SMTP **mx2.windy.me** / **587** / **starttls**, `ip_header` **X-Forwarded-For**. Also confirm `.env` and `config.json` SMTP passwords match (lengths/hashes only — never print secrets): ```bash ssh -4 windy@us2.wsvc.info 'cd /opt/vaultwarden && python3 - << "EOF" import json, re, pathlib, hashlib, subprocess env = pathlib.Path(".env").read_text() def grab(k): m = re.search(rf"^{re.escape(k)}=(.*)$", env, re.M) return m.group(1).strip().strip("\"'\''") if m else None cfg = json.loads(subprocess.check_output( ["docker","compose","exec","-T","vaultwarden","cat","/data/config.json"])) ep, cp = grab("SMTP_PASSWORD"), cfg.get("smtp_password") or "" def fp(s): return {"len": len(s), "sha256_8": hashlib.sha256(s.encode()).hexdigest()[:8]} print("env_pass", fp(ep or ""), "cfg_pass", fp(cp), "match", ep == cp) EOF' ``` If they diverge, **auth uses `config.json`** — sync password there (or via `/admin`) and update `.smtp-credentials`. ### 4. External HTTPS ```bash curl -4 -sS -I --max-time 15 https://auth.wsvc.info/ | head -15 ``` If **404** right after recreating VW: use the Ansible Compose reconcile playbook with `service_reconcile_restart_traefik=true`, then retry. ### 5. SMTP reachability + AUTH TCP (expect `587:0`; `465` often times out from us2): ```bash ssh -4 windy@us2.wsvc.info 'docker compose -f /opt/vaultwarden/docker-compose.yml exec -T vaultwarden sh -c "timeout 5 bash -c \"cat /dev/tcp/mx2.windy.me/587\"; echo 587:\$?"' ``` AUTH with the **effective** `config.json` password (run on us2; do not print the password): ```bash ssh -4 windy@us2.wsvc.info 'cd /opt/vaultwarden && python3 - << "EOF" import json, smtplib, ssl, subprocess cfg = json.loads(subprocess.check_output( ["docker","compose","exec","-T","vaultwarden","cat","/data/config.json"])) host, port = cfg["smtp_host"], int(cfg["smtp_port"]) user, pw = cfg["smtp_username"], cfg["smtp_password"] ctx = ssl.create_default_context() with smtplib.SMTP(host, port, timeout=15) as s: s.ehlo(); s.starttls(context=ctx); s.ehlo(); s.login(user, pw) print("AUTH_OK", host, port, user) EOF' ``` Expect `AUTH_OK`. `535` usually means stale password in `config.json` (see step 3). ## Scheduled local check The sanitized Ansible health profile is `vaultwarden` (`ansible/playbooks/healthchecks.yml`). It runs locally through `vps-healthcheck.timer`, writes a sanitized JSON result to `/var/lib/vps-health/latest.json`, and uses only server-side credentials for the SMTP AUTH probe. It does not modify Vaultwarden, Traefik, SMTP, or secrets. ## Pass criteria - Healthy compose; `DATABASE_URL` points at `pg` - HTTPS 200 + `/admin` 200; PG counts sane - Effective SMTP 587/starttls; `.env` ↔ `config.json` password match; **AUTH_OK** - Wrong-password login returns 400 business error (not 500) - Update **Verified** on [hosts/us2.wsvc.info.md](../hosts/us2.wsvc.info.md)