Vaultwarden на VPS: як розгорнути свій менеджер паролів
Розгорніть сумісний із Bitwarden менеджер паролів через Docker на VPS: HTTPS до першого входу, admin token, Fail2ban і перевірені резервні копії.
Що ви створюєте
Менеджер паролів, яким ви повністю володієте: Vaultwarden працює в одному невеликому контейнері за reverse proxy, який завершує HTTPS, а офіційні застосунки Bitwarden на телефоні, ноутбуці та в браузері підключені до нього. Vaultwarden повторно реалізує серверний API Bitwarden мовою Rust і використовує той самий протокол, що й bitwarden.com. Тому всі офіційні клієнти працюють із ним без змін. Водночас він використовує близько 100 MB RAM замість офіційного стека з кількох контейнерів.
Сама інсталяція складається з десятка рядків Compose. Насправді важливі лише три речі, через які найчастіше виникають проблеми: TLS має бути налаштований до першого відкриття вебсховища, публічну реєстрацію потрібно закрити одразу після створення власного облікового запису, а том із даними потрібно резервувати та перевіряти відновленням. Саме в цьому одному каталозі зберігаються всі ваші паролі.
Передумови та важливі обмеження
- VPS із Docker Engine і плагіном Compose на чистому Ubuntu 24.04 у KVM із root або sudo. 512 MB RAM справді достатньо; 1 GB забезпечує комфортний запас. Це один із найлегших сервісів для запуску, він посідає чільне місце в короткому списку сервісів, які варто розгортати самостійно. Однак розраховуйте розмір VPS з урахуванням інших сервісів: розміщення self-hosted фототеки на кшталт PhotoPrism або Immich на тому самому VPS підвищує мінімальну потребу в RAM до кількох гігабайтів, тоді як Vaultwarden майже не збільшує її. Такий самий розрахунок стосується медіафронтендів, які ви додасте пізніше, оскільки перетворення бібліотеки Jellyfin на інтерактивний відеопрокат у стилі 90-х означає ще один постійно запущений контейнер і додатковий запас ресурсів для транскодування в межах того самого бюджету.
- Домен із записом A (і AAAA, якщо у вас є IPv6), що вказує
vault.example.comна VPS. TLS-сертифікат видається для цього конкретного імені, тому DNS має повертати потрібну адресу до початку налаштування. - Порти 80 і 443, відкриті для інтернету та оброблювані reverse proxy, ніколи не самим Vaultwarden. Порт 80 використовується лише для ACME-перевірки сертифіката та перенаправлення HTTP на HTTPS.
- Найважливіше обмеження: клієнти Bitwarden відмовляються підключатися до сервера без HTTPS. Варіант «спочатку перевірити через http» не працює. Причину цього пояснено в наступному розділі.
Чому Vaultwarden, а не офіційний стек Bitwarden
Ті самі клієнти, але значно менше ресурсів. Офіційний self-hosted Bitwarden постачається як набір контейнерів (MSSQL, Nginx, Identity, Api, Admin та інші) і потребує приблизно 2 GB RAM. Vaultwarden — це один бінарний файл, який за замовчуванням зберігає всі дані в базі SQLite і в режимі простою споживає лише кілька десятків мегабайт. Для однієї людини, сім’ї або невеликої команди це очевидний вибір. Оскільки Vaultwarden коректно реалізує API Bitwarden, дані залишаються переносимими між ним і bitwarden.com.
Ви втрачаєте більшість enterprise-функцій: немає provisioning через SCIM (хоча в 1.35.0 з’явилася експериментальна підтримка SSO через OpenID Connect). Крім того, оператором є ви, тому оновлення, HTTPS і резервне копіювання — ваша відповідальність. Цей посібник присвячено саме цим трьом завданням.
Чому HTTPS є обов’язковим
Вебсховище Bitwarden і розширення для браузерів отримують ключі шифрування в браузері за допомогою Web Crypto API (window.crypto.subtle). Браузери надають доступ до crypto.subtle лише в безпечному контексті: через HTTPS або у спеціальному випадку http://localhost. Через звичайний http://vault.example.com це undefined, тому під час першого отримання ключа застосунок аварійно завершує операцію, а в консолі з’являється:
Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'importKey')Сторінка зависає або показує загальну помилку криптографії, і вхід не виконується. Клієнти для desktop, mobile і браузерів самостійно перевіряють self-hosted URL. Для endpoint із http або недоступного endpoint вони відмовляються працювати з повідомленням:
This is not a recognized Bitwarden server. You may need to check with your provider or update your server.Причина в обох випадках одна: немає коректного HTTPS. Тому спочатку налаштовуємо TLS і ніколи не відкриваємо vault через http, навіть один раз для швидкої перевірки.
Крок 1. DNS і reverse proxy (спочатку TLS)
Спрямуйте запис на свій VPS і переконайтеся, що він розв’язується в правильну адресу:
dig +short vault.example.comВиведений рядок має містити IP-адресу вашого VPS. Якщо рядок порожній або містить неправильну адресу, виправте DNS і дочекайтеся завершення TTL. Інакше випуск сертифіката завершиться помилкою, оскільки ім’я не розв’язується.
Для HTTPS front end у цьому посібнику використовується Traefik. Він автоматично випускає та поновлює сертифікати Let's Encrypt і безпосередньо інтегрується з Compose. Якщо ви ще не використовуєте його, спочатку виконайте налаштування reverse proxy Traefik та автоматичного TLS. Воно створює зовнішню Docker network (proxy нижче) і ACME resolver (letsencrypt), до якого підключається сервіс Vaultwarden. Звичайний nginx із сертифікатом, випущеним вручну, працює для Vaultwarden так само.
Віддаєте перевагу nginx і Certbot замість Traefik? Підключіть Vaultwarden до 127.0.0.1:8080 (додайте ports: ["127.0.0.1:8080:80"] до сервісу та видаліть labels Traefik), потім випустіть сертифікат і налаштуйте proxy до цього сервісу. Випуск сертифіката описано в розділі випуск сертифікатів Let's Encrypt за допомогою Certbot і nginx. Критично важливо додатково налаштувати WebSocket upgrade для notifications path:
server {
listen 443 ssl;
server_name vault.example.com;
client_max_body_size 525M;
location / {
proxy_pass http://127.0.0.1:8080;
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;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}Зверніть увагу на рядок X-Real-IP. Саме він дає змогу Fail2ban надалі бачити реальну IP-адресу зловмисника, а не 127.0.0.1. Усе інше в цьому посібнику однакове незалежно від того, Traefik чи nginx працює перед сервісом.
Крок 2, файл Compose
Спочатку створіть каталог проєкту. У цьому посібнику використовується /opt/vaultwarden. Завдяки цьому ім’я проєкту Compose і, відповідно, том даних vaultwarden_vw-data залишаються передбачуваними. Наведені нижче кроки для Fail2ban і резервного копіювання залежать від цього точного імені.
sudo mkdir -p /opt/vaultwarden
cd /opt/vaultwardenСтворіть у цьому каталозі .env для секрету адміністратора та файл Compose.
# .env
ADMIN_TOKEN=paste-a-strong-token-hereЗгенеруйте цей токен за допомогою openssl rand -base64 48 і вставте його у файл. Наступний розділ описує надійнішу хешовану форму; для початку достатньо довгого випадкового рядка.
# docker-compose.yml
services:
vaultwarden:
image: vaultwarden/server:latest
container_name: vaultwarden
restart: unless-stopped
environment:
DOMAIN: "https://vault.example.com"
SIGNUPS_ALLOWED: "true" # closed in Step 4, keep true just to register
ADMIN_TOKEN: "${ADMIN_TOKEN}"
IP_HEADER: "X-Forwarded-For" # X-Real-IP if your proxy sends that instead
LOG_FILE: "/data/vaultwarden.log"
LOG_LEVEL: "warn"
volumes:
- vw-data:/data
networks:
- proxy
labels:
- "traefik.enable=true"
- "traefik.http.routers.vw.rule=Host(`vault.example.com`)"
- "traefik.http.routers.vw.entrypoints=websecure"
- "traefik.http.routers.vw.tls.certresolver=letsencrypt"
- "traefik.http.services.vw.loadbalancer.server.port=80"
volumes:
vw-data:
networks:
proxy:
external: trueЦей файл містить два ключові елементи всієї схеми. Тут немає зіставлення ports:, тому Vaultwarden доступний лише через Traefik і його TLS. Публікація порту Vaultwarden на хості призводить до того, що сховище випадково стає доступним через http. Крім того, DOMAIN має бути повною публічною HTTPS-URL-адресою. Вона вбудовується в посилання на вкладення, 2FA WebAuthn і endpoint сповіщень. Тому неправильне значення або значення з http порушить їхню роботу, навіть якщо сайт завантажується. Тег latest є свідомим винятком із загального правила ніколи не використовувати latest. Vaultwarden випускає стабільні версії як один rolling image, а :testing є окремим каналом попередніх версій. Тому оновлюйте його навмисно та переглядайте release notes перед завантаженням образу. Виняток має вузьке застосування. Більшість довгоживучих контейнерів краще прив’язувати до точного тегу. Саме це забезпечує передбачувану роботу постійно активного self-hosted агента на тому самому VPS після перезавантажень і повторного завантаження образів.
Запустіть стек і стежте за журналом:
docker compose up -d
docker compose logs -f vaultwardenКоректний запуск завершується рядком на кшталт Rocket has launched from http://0.0.0.0:80. Зачекайте кілька секунд, поки Traefik отримає сертифікат, а потім відкрийте https://vault.example.com. Ви маєте побачити вебсховище Bitwarden із дійсним сертифікатом і без попередження про сертифікат.
Крок 3: надійний ADMIN_TOKEN і пастка з $$
ADMIN_TOKEN захищає /admin — панель, яка може читати дані кожного користувача та всі налаштування у вашому екземплярі. Тому поводьтеся з ним як із паролем root. Працюють два варіанти.
Простий варіант — випадковий рядок, який ви вже згенерували за допомогою openssl rand -base64 48. Оскільки base64 ніколи не містить $, його можна безпосередньо вставити в .env без екранування.
Посилений варіант — хеш Argon2 PHC, тому токен у відкритому вигляді не зберігається на диску. Згенеруйте його на основі того самого image:
docker run --rm -it vaultwarden/server /vaultwarden hash --preset owaspКоманда двічі запитує токен і виводить рядок, що починається з $argon2id$v=19$.... Тут є пастка, яка може забрати годину: Docker Compose сприймає $ як підстановку змінної, тому під час вставлення хешу у файл Compose потрібно подвоїти кожен $ до $$. Розмістіть його безпосередньо під environment:, а не через .env, і не беріть його в лапки:
environment:
ADMIN_TOKEN: $$argon2id$$v=19$$m=19456,t=2,p=1$$c29tZXNhbHQ$$RdescudvJCsgt3ub+b+dWRWJTmaaJObGЯкщо залишити одиночні знаки $, Compose виведе попередження The "argon2id" variable is not set і очистить значення токена, після чого /admin відхилить правильний пароль. Виконайте docker compose up -d і збережіть токен у відкритому вигляді, який ви ввели під час запиту, у власному менеджері паролів.
Крок 4. Зареєструйте обліковий запис і закрийте доступ до реєстрації
За допомогою SIGNUPS_ALLOWED: "true" відкрийте https://vault.example.com, натисніть Create account і зареєструйтеся за допомогою своєї електронної адреси та надійного master password. Відновити цей master password неможливо. Скидання не передбачено, тому спочатку збережіть його в надійному місці.
Тепер вимкніть реєстрацію. Відредагуйте Compose-файл:
SIGNUPS_ALLOWED: "false"Повторно застосуйте зміни за допомогою docker compose up -d. Це налаштування безпеки не можна відкладати. Якщо реєстрацію залишити відкритою, будь-хто, хто знайде URL, зокрема crawler, зможе створити обліковий запис на вашому сервері. Ці користувачі не зможуть прочитати ваше сховище, але споживатимуть ресурси та перетворять ваш приватний інстанс на загальнодоступний сервіс. Ознака того, що реєстрацію не вимкнено: /admin містить облікові записи, які ви не створювали.
Щоб пізніше додати членів сім’ї або колег без повторного відкриття загальнодоступної реєстрації, скористайтеся кнопкою Invite User у /admin. Для цього потрібно налаштувати SMTP, щоб запрошений користувач отримав посилання.
Крок 5: доступ до /admin
Перейдіть до https://vault.example.com/admin і введіть відкритий токен адміністратора (випадковий рядок або пароль, для якого ви створили хеш, але не сам хеш). У панелі можна переглядати користувачів, змінювати параметри, надсилати тестовий email і створювати знімок бази даних.
Якщо сторінка повертає 404 Not Found, параметр ADMIN_TOKEN порожній або не заданий, тому панель повністю вимкнена. Це також припустимий варіант, якщо панель вам ніколи не потрібна. Якщо панель завантажується, але відхиляє ваш токен, перегляньте проблему з екрануванням $$ у наведеному нижче списку помилок. Забули токен? Функції відновлення немає. Відредагуйте .env або Compose-файл, задайте новий токен і docker compose up -d.
Крок 6. Підключіть клієнти Bitwarden
Кожен офіційний клієнт може підключатися до self-hosted сервера. Встановіть клієнт Bitwarden для desktop, mobile або browser зі звичайного магазину. Спеціальна збірка Vaultwarden не потрібна.
Перед входом відкрийте меню налаштувань на екрані входу (пункт Self-hosted або Region → Self-hosted), у полі Server URL вкажіть https://vault.example.com і збережіть зміни. Потім увійдіть за допомогою зареєстрованої email-адреси та master password. Клієнт має одразу підключитися й запропонувати заповнювати та зберігати облікові дані.
Якщо клієнт показує This is not a recognized Bitwarden server. You may need to check with your provider or update your server., URL неправильний, використовується http або сертифікат не вважається довіреним. Спочатку перевірте в браузері, що https://vault.example.com відкривається без помилок. Повільне оновлення на інших пристроях пов’язане з WebSocket push; це описано нижче.
Крок 7, jail Fail2ban для endpoint входу
Vaultwarden записує кожну невдалу спробу входу у файл, заданий через LOG_FILE. Це саме те, що потрібно для захисту від brute-force атак. Якщо Fail2ban ще не запущено, інструкції з інсталяції та базового налаштування наведено в посібнику з посилення захисту SSH за допомогою Fail2ban. Тут ми додамо один jail для vault.
Спочатку знайдіть, де на хості розташований named volume, щоб Fail2ban міг читати журнал:
docker volume inspect vaultwarden_vw-data --format '{{ .Mountpoint }}'Команда виведе приблизно таке: /var/lib/docker/volumes/vaultwarden_vw-data/_data. Файл журналу всередині volume має шлях vaultwarden.log. Створіть filter:
# /etc/fail2ban/filter.d/vaultwarden.conf
[Definition]
failregex = ^.*Username or password is incorrect\. Try again\. IP: <ADDR>\. Username:.*$
ignoreregex =Потім створіть jail:
# /etc/fail2ban/jail.d/vaultwarden.local
[vaultwarden]
enabled = true
filter = vaultwarden
logpath = /var/lib/docker/volumes/vaultwarden_vw-data/_data/vaultwarden.log
banaction = iptables-allports
chain = DOCKER-USER
maxretry = 5
findtime = 600
bantime = 3600Перезавантажте конфігурацію за допомогою sudo systemctl restart fail2ban і перевірте результат командою sudo fail2ban-client status vaultwarden.
Є три особливості Docker, від яких залежить, чи забезпечить це захист. По-перше, якщо в журналі під час кожної невдалої спроби відображається IP: 127.0.0.1 або адреса вашого proxy, Vaultwarden блокує proxy. Установіть IP_HEADER у значення заголовка, який фактично надсилає ваш proxy: X-Forwarded-For для Traefik, X-Real-IP для наведеного вище блоку nginx, CF-Connecting-IP у разі використання Cloudflare. По-друге, правильний ланцюжок iptables залежить від вашого proxy. Якщо Traefik працює як container із published ports, трафік проходить шляхом Docker FORWARD, тому блокування має застосовуватися в DOCKER-USER, як показано вище. Якщо ж на Кроці 1 ви вибрали варіант із nginx на хості, з’єднання завершуються в nginx у ланцюжку INPUT хоста, а блокування DOCKER-USER їх не бачить. У такому разі видаліть рядок chain = DOCKER-USER, щоб Fail2ban використовував типовий ланцюжок INPUT. По-третє, використовуйте banaction = iptables-allports замість типового параметра на основі порту. Цей jail не визначає порт, а блокування всіх портів у DOCKER-USER коректно забороняє порушнику доступ до кожного опублікованого сервісу на цьому хості.
Крок 8. Створіть резервну копію сховища, а потім фактично відновіть його
Том vw-data є вашим менеджером паролів. У ньому зберігаються db.sqlite3 (кожен запис), каталоги attachments/ і sends/, файли rsa_key.*, які підписують сеанси входу, а також config.json з панелі адміністратора. Резервна копія, у якій відсутній будь-який із цих компонентів, не допоможе під час відновлення.
Копіювання db.sqlite3 під час запису Vaultwarden може зберегти частково записаний і пошкоджений файл, тому створіть холодний знімок. Простій триватиме кілька секунд:
#!/usr/bin/env bash
set -euo pipefail
STAMP=$(date +%F)
DEST=/root/vw-backups
VOL=$(docker volume inspect vaultwarden_vw-data --format '{{ .Mountpoint }}')
mkdir -p "$DEST"
docker compose -f /opt/vaultwarden/docker-compose.yml stop vaultwarden
tar czf "$DEST/vw-$STAMP.tgz" -C "$VOL" .
docker compose -f /opt/vaultwarden/docker-compose.yml start vaultwardenЗапускайте це завдання щоночі з cron і копіюйте .tgz за межі сервера. Резервна копія, яка зберігається лише на сервері, який ви захищаєте, не є резервною копією. Надійний спосіб передавання — щоночі створювати резервну копію restic на іншому сервері або в об’єктному сховищі. Архів шифрується, а повторювані знімки дедуплікуються автоматично. Кнопка Backup Database в панелі адміністратора зручно створює гарячу копію лише файлу SQLite, але не містить вкладень і ключів.
Тепер виконайте процедуру, яка відрізняє справжню резервну копію від неперевіреної: один раз відновіть її та переконайтеся, що все працює:
mkdir -p /tmp/vw-restore
tar xzf /root/vw-backups/vw-2026-07-15.tgz -C /tmp/vw-restore
docker run --rm -p 127.0.0.1:8888:80 -v /tmp/vw-restore:/data vaultwarden/serverЗ ноутбука підключіться до нього через тунель за допомогою ssh -L 8888:127.0.0.1:8888 you@your-vps і відкрийте http://localhost:8888. Оскільки localhost є захищеним контекстом, crypto.subtle доступний, а сховище тут розшифровується через звичайний http — це єдине місце, де це дозволено. Увійдіть за допомогою головного пароля та переконайтеся, що ваші записи присутні. Якщо вони є, база даних, ключі RSA і головний пароль успішно відновлюються, а новий VPS можна відновити за кілька хвилин. Зупиніть контейнер за допомогою Ctrl-C і видаліть /tmp/vw-restore. Використовуйте цей спосіб тунелювання для будь-якого іншого інтерфейсу адміністрування на сервері, який не повинен бути доступним з Інтернету. Так само можна отримати доступ до self-hosted сканера безпеки open-kritt на порту 5173.
Типові відмови та повідомлення, які ви побачите
Cannot read properties of undefined (reading 'importKey') у консолі браузера. Сховище було завантажено через http, тому crypto.subtle не визначено. Відкривайте його лише через https:// і додайте на проксі перенаправлення з HTTP на HTTPS.
This is not a recognized Bitwarden server... у клієнті. Server URL використовує http, містить помилку або сертифікат не є довіреним. Переконайтеся, що https://vault.example.com показує дійсний замок, а потім повторно введіть цю адресу в налаштуваннях self-hosted у клієнті.
/admin відхиляє правильний пароль. Хеш Argon2 втратив екранування. У Compose кожен $ має бути $$, або ви ввели хеш замість відкритого тексту пароля, якому він відповідає.
Повільна синхронізація між пристроями; консоль показує WebSocket connection to 'wss://vault.example.com/notifications/hub' failed. Проксі не передає заголовки Upgrade/Connection. Traefik робить це автоматично, а для nginx потрібні два рядки upgrade з Step 1. Сховище продовжує працювати, але синхронізація виконується лише під час відкриття. Старий окремий порт 3012 видалено починаючи з v1.31.0, тому окремий маршрут WebSocket не потрібен.
Fail2ban повідомляє про блокування, але зловмисник продовжує підключатися. Блокується 127.0.0.1, оскільки IP_HEADER налаштовано неправильно, або блокування застосовано не в тому ланцюжку iptables. Установіть chain = DOCKER-USER і banaction = iptables-allports.
Оновлення
Завантажте новий образ і пересоздайте контейнер. Іменований том і всі ваші дані збережуться:
docker compose pull
docker compose up -dVaultwarden часто випускає нові версії. Стежте за примітками до релізів проєкту, а не фіксуйте версію patch, оскільки деякі релізи містять примітки щодо міграції. Перед будь-яким значним оновленням створіть свіжу резервну копію. Відкотити зміни можна, відновивши tarball у новий том.
FAQ
Vaultwarden — це те саме, що Bitwarden?
Це сумісний незалежний сервер, а не офіційний сервер. Vaultwarden повторно реалізує серверний API Bitwarden мовою Rust, тому офіційні клієнти для desktop, mobile, browser і CLI працюють із ним, споживаючи значно менше ресурсів, ніж офіційний стек. Формат сховища такий самий, тому міграцію можна виконати в обох напрямках за допомогою експорту й імпорту.
Чи справді потрібен HTTPS, чи можна використовувати http у локальній мережі?
HTTPS потрібен для всього, крім тесту localhost. Вебсховище та розширення Bitwarden використовують Browser Web Crypto API, доступний лише в безпечному контексті. Тому через звичайний http клієнт видає помилку Cannot read properties of undefined і не виконує вхід. Єдина адреса http, яка працює, — http://localhost. Саме тому в тесті відновлення на кроці 8 використовується SSH-тунель.
Як заборонити стороннім користувачам реєструватися на моєму сервері?
Установіть SIGNUPS_ALLOWED: "false" у Compose-файлі та виконайте docker compose up -d одразу після створення власного облікового запису. Після цього додавайте нових користувачів за допомогою кнопки Invite User у /admin. Для цього потрібно налаштувати SMTP, щоб вони отримували посилання-запрошення. Час від часу перевіряйте список користувачів адміністратора, щоб переконатися, що не з’явилися неочікувані облікові записи.
Як створити резервну копію сховища Vaultwarden?
Ненадовго зупиніть контейнер і заархівуйте весь том vw-data, db.sqlite3, attachments/, sends/, config.json і файли rsa_key.*. Потім скопіюйте архів із сервера, бажано за допомогою нічного cron. Копіювання активного SQLite-файлу під час роботи сервера може призвести до пошкодженого знімка, тому створюйте резервну копію після зупинки сервера. Найважливіше — один раз відновіть її в тимчасовому контейнері та виконайте вхід, щоб переконатися, що резервна копія справна, перш ніж покладатися на неї.
Чи справді безпечно самостійно розміщувати паролі?
Так, якщо виконати три вимоги, описані в цьому посібнику: використовувати справжній HTTPS, закрити реєстрацію, налаштувати надійний токен адміністратора та перевірити резервні копії. Ваше сховище шифрується на стороні клієнта майстер-паролем. Тому навіть сервер не бачить ваші паролі у відкритому вигляді, а викрадений db.sqlite3 без нього не дає змоги ними скористатися. Недоліком є те, що встановлення оновлень і створення резервних копій тепер стають вашою відповідальністю. Саме тому Fail2ban і процедура відновлення тут є обов’язковими. Після цього корисним наступним кроком буде детальніше розглянути, де саме можна атакувати самостійно розміщене сховище, оскільки самі записи шифруються на стороні клієнта, а захищати потрібно токен адміністратора та архів резервної копії.