Серверная под лестницей

Что я тут поднимаю и роняю

Vaultwarden — свой Bitwarden

6 мая 2026

Пользовался Bitwarden Cloud полтора года, претензий не было. Решил всё-таки переехать к себе — больше из принципа, чем по реальной необходимости. Vaultwarden — совместимая с Bitwarden реализация на Rust, лёгкая, влезает в 100 MB RAM. Клиенты те же: расширения для браузеров, мобильные приложения, всё работает без правок.

compose.yml

Структура папки — как в прошлом посте (один сервис = одна папка):

services:
  app:
    image: vaultwarden/server:1.32.5
    container_name: vaultwarden
    restart: unless-stopped
    ports:
      - "127.0.0.1:8080:80"
    env_file: .env
    volumes:
      - ./data:/data

В .env лежит то, что не хочу хардкодить в compose:

DOMAIN=https://vault.myhome.example
SIGNUPS_ALLOWED=false
ADMIN_TOKEN=<argon2-хеш>
PUSH_ENABLED=false
SHOW_PASSWORD_HINT=false

SIGNUPS_ALLOWED=false

Vaultwarden по умолчанию открыт: любой, кто попадёт на URL, создаёт себе аккаунт. На публичном домене это плохая идея. Сначала зарегался сам, потом сразу выключил регистрацию.

Если в будущем понадобится добавить пользователя, есть INVITATIONS_ALLOWED — приглашение по ссылке, без открытой регистрации.

ADMIN_TOKEN — обязательно argon2-хеш

В Vaultwarden есть /admin UI для диагностики и управления. Доступ — по ADMIN_TOKEN.

В старых версиях ADMIN_TOKEN задавался plain-строкой. Если .env утёк — у злоумышленника сразу админка. С 1.30 поддерживается argon2:

echo -n 'мой-секрет-админа' | argon2 \
  "$(openssl rand -base64 32)" -e -id -k 65540 -t 3 -p 4

В .env идёт хешированное значение. Plain секрет лежит у меня же в Vaultwarden — рекурсивно, да.

DOMAIN

Без правильно прописанного DOMAIN не работают push-уведомления и WebAuthn. Полный URL с протоколом, без trailing slash.

Reverse proxy

127.0.0.1:8080 наружу не торчит. Чтобы открыть, нужен reverse proxy с TLS — настройка отдельная, не буду в этом посте, и так длинно. Главное в проксе — пробросить X-Forwarded-Proto и X-Forwarded-For, иначе Vaultwarden будет ругаться на mismatch протоколов.

Бэкап

База — это sqlite-файл data/db.sqlite3 плюс пара папок с аттачами и сендами. Бэкап через systemd-timer, раз в сутки в 04:00:

#!/bin/bash
set -euo pipefail
DATE=$(date +%F)
cd /opt/services/vaultwarden
sqlite3 data/db.sqlite3 ".backup data/db.sqlite3.bak"
tar czf /var/backups/vaultwarden-$DATE.tar.gz \
  data/db.sqlite3.bak data/attachments data/sends
rm data/db.sqlite3.bak

.backup через sqlite3 безопаснее cp на живой базе — гарантирует консистентность даже если в этот момент идёт запись.

Старые бэкапы чищу через find -mtime в отдельном скрипте раз в неделю.

Миграция из Bitwarden Cloud

Делается из веба. Settings → Tools → Export vault → JSON (encrypted), скачивается файл. Потом в Vaultwarden: Tools → Import data → Bitwarden (json) → выбрать файл.

Vault'ы переезжают как есть, включая folders. Аттачи приходится переименовать руками — Bitwarden Cloud экспортит их с генерёнными именами, после импорта они называются как угодно. У меня их было пять штук, перенёс руками.

После миграции — выходишь из Bitwarden Cloud со всех устройств, ставишь свой сервер в клиентах, входишь заново. Браузерные расширения и мобильное приложение поддерживают custom server URL — настройка на странице логина, до ввода email.

Что в итоге

Поставил позавчера, базу залил из Bitwarden Cloud, клиенты подружились. Пока работает. Память — 80 MB, диск вместе с парой десятков паролей и пятью аттачами — 12 MB. По сравнению с облачной нет push-уведомлений на новые входы (нужен отдельный Bitwarden Push Server, я его не ставил).

Дальше думаю про Memos — короткие заметки, типа Twitter для самого себя.