У невеликому офісі паролі живуть усюди, крім правильного місця: у приватних повідомленнях, у спільній табличці, на стікері під клавіатурою, у голові в одного співробітника, який сьогодні на лікарняному. А коли хтось звільняється, ніхто навіть не пам’ятає, до чого в нього був доступ. Знайомо.
Рішення давно є - командний менеджер паролів. Але хмарні тарифи рахують гроші за кожне місце, а віддавати всі паролі компанії стороннього сервісу хочеться не завжди. Vaultwarden - це легкий сервер, повністю сумісний із застосунками Bitwarden (ті самі розширення для браузера, десктоп, мобільний), який ти ставиш на власну машину. Написаний на Rust, у спокої з’їдає якісь 10-30 МБ пам’яті, а місць не рахує взагалі - хоч 20 людей, хоч 200.
Це перший пост нової серії «Офіс без адміна»: ставимо корисні сервіси так, щоб їх потягнула людина без фаху системного адміністратора. Сьогодні - власний сейф для паролів команди, від порожньої VM до працюючих клієнтів на столах.
Що потрібно зробити?#
Підняти Vaultwarden на одній маленькій віртуалці для офісу приблизно на 20 людей:
- винести дані сейфа на окремий диск - щоб бекапити й переносити їх окремо від системи;
- поставити Docker і запустити зв’язку Vaultwarden + Caddy одним
docker compose; - зробити HTTPS без публічного домену - клієнти Bitwarden інакше просто не працюють;
- створити організацію (спільний сейф), колекції по відділах і запросити команду;
- підключити клієнти на пристроях і насамкінець замкнути реєстрацію, щоб нові акаунти не заводив хто завгодно.
З чим працюю?#
Одна скромна віртуалка - рівно стільки, скільки треба, і ні гігабайтом більше:
- 1 vCPU, 1 ГБ RAM;
- 10 ГБ системного диска + окремий 5 ГБ диск під дані;
- ОС Ubuntu 26.04 LTS.
Vaultwarden настільки економний, що 1 ГБ пам’яті на 20 користувачів - із великим запасом. Для певності додамо ще 1 ГБ swap, щоб система мала куди відступити під час пікового навантаження чи оновлень.
Сервер у прикладі доступний у локальній мережі за адресою 192.168.1.50 - публічного домену в нас поки немає, і це окрема історія, до якої ще дійдемо. Усюди далі підставляй свою адресу.
Окремий диск під дані#
Перше правило домашнього (та й не лише домашнього) сервера: дані не повинні лежати там само, де система. Коли сейф із паролями живе на окремому диску, його легко забекапити, збільшити чи перенести на іншу машину - не чіпаючи саму ОС. Тому під Vaultwarden ми й підкинули віртуалці другий диск на 5 ГБ.
У системі він з’являється порожнім пристроєм /dev/sdb. Форматуємо його в ext4 з міткою vwdata, створюємо точку монтування й монтуємо:
| |
Щоб диск підмонтувався сам після перезавантаження, додаємо його в /etc/fstab - і обов’язково по UUID, а не по /dev/sdb (імена пристроїв можуть помінятися місцями). UUID підкаже blkid:
| |
Рядок у /etc/fstab (підстав свій UUID):
| |
Перевіряємо, що запис коректний, одразу монтуючи все з fstab:
| |
Якщо команда відпрацювала без помилок і /mnt/data змонтувався - диск готовий і переживе перезавантаження.
Поки ми тут, додамо обіцяний 1 ГБ swap. Це дешева страховка для машини з гігабайтом пам’яті:
| |
І теж закріплюємо в /etc/fstab, щоб swap вмикався сам:
| |
Docker з репозиторіїв Ubuntu#
Vaultwarden ми запустимо в контейнері - це найпростіший спосіб тримати його оновленим і не захламлювати систему. Зазвичай у гайдах радять підключати офіційний репозиторій Docker, але для нашої задачі це зайве: у штатних репозиторіях Ubuntu 26.04 лежить цілком свіжий Docker. Ставимо двома пакетами:
| |
Це дає Docker 29.x і сучасний docker compose версії 2.40.x (саме docker compose, двома словами, а не старий docker-compose). Більше нічого підключати не треба.
Vaultwarden і Caddy одним compose-файлом#
Складемо весь сервіс в одній папці /opt/vaultwarden. Там буде три файли: .env із секретами, docker-compose.yml зі самими контейнерами і Caddyfile для HTTPS.
| |
Токен адмінки: argon2, а не текст#
У Vaultwarden є сторінка /admin - звідти керують сервером. Доступ до неї закриває ADMIN_TOKEN. Раніше туди писали пароль відкритим текстом, але так робити вже не можна: сучасний Vaultwarden на це сварливо попереджає й хоче argon2-хеш. Згенерувати його допомагає сам образ Vaultwarden:
| |
Команда двічі спитає пароль адмінки й надрукує довгий рядок виду $argon2id$v=19$m=.... Це і є токен. Скопіюй його повністю.
Файл .env#
Хеш argon2 рясніє знаками $, а compose трактує $ як підстановку змінної. Тому токен кладемо не в docker-compose.yml, а в окремий .env - і беремо значення в одинарні лапки, щоб жоден $ не з’їло:
| |
Кілька слів про кожен рядок:
DOMAIN- точна адреса, за якою клієнти доходять до сервера, разом зіhttps://. Вона має збігатися з тим, що ти введеш у застосунках, інакше частина функцій зламається.ADMIN_TOKEN- той самий argon2-рядок зі згенерованого хеша.SIGNUPS_ALLOWED=true- дозвіл на самостійну реєстрацію. Вмикаємо його тільки на час, поки заводимо перші акаунти, а потім вимкнемо.
Файл із секретами не повинні читати всі підряд:
| |
docker-compose.yml#
| |
Головне тут: версія Vaultwarden зафіксована на 1.37.4 (щоб оновлення не прилетіло несподівано), а дані сейфа лежать на нашому окремому диску - /mnt/data/vaultwarden усередині контейнера видно як /data. Поруч стоїть Caddy - він прийматиме HTTPS ззовні й передаватиме запити Vaultwarden. LOG_FILE ми виставили свідомо: пізніше по цьому логу можна буде нацькувати fail2ban.
Caddyfile: HTTPS без домену#
А тепер найцікавіша частина, на якій багато хто спотикається. Клієнти Bitwarden і Vaultwarden шифрують сейф у браузері через Web Crypto API, а він працює тільки в захищеному (https) контексті. Обхідного шляху через звичайний http немає - без HTTPS застосунки просто відмовляться працювати.
Зазвичай сертифікат видає Let’s Encrypt, але для цього потрібен публічний домен. У нас його немає - сервер живе в локальній мережі за IP. Рятує Caddy: він уміє бути власним центром сертифікації й сам видати собі сертифікат. Ось весь Caddyfile:
| |
tls internal і каже Caddy стати власним CA. А default_sni тут не прикраса, а необхідність: коли клієнт стукає по IP-адресі, він не надсилає ім’я сервера (SNI), і Caddy не знає, який сертифікат показувати. Без цього рядка TLS-рукостискання просто обривається - класична заковика. Ми явно кажемо: за замовчуванням показуй сертифікат для 192.168.1.50.
Запускаємо все:
| |
Роздаємо довіру#
Є один компроміс життя без домену: сертифікат видав наш власний CA, якого браузери поки не знають, тож спершу вони лаятимуться «не захищено». Щоб цього не було, кореневий сертифікат Caddy треба раз установити на офісні пристрої. Лежить він тут:
| |
Цей root.crt розносимо по пристроях команди один раз:
- Linux - покласти в довірені й виконати
update-ca-certificates; - Windows / macOS - додати сертифікат у системне сховище довіри;
- мобільні - встановити як профіль сертифіката.
Перевірити, що сервер відповідає по HTTPS, можна й не чекаючи роздачі - просто вказавши curl наш кореневий сертифікат:
| |
У відповідь прилетить мітка часу й код 200 - сервер живий. У самому сертифікаті побачиш, що він виданий на IP сервера, а видавець - Caddy Local Authority.
Коли згодом у офісу з’явиться справжній домен, уся ця морока зникає: у Caddyfile замінюєш IP на доменне ім’я й прибираєш рядок tls internal - Caddy сам візьме безкоштовний сертифікат у Let’s Encrypt, і розносити нічого не доведеться.
«Офіс»: організація та колекції#
Сервер працює - час перетворити його на спільний сейф. У Vaultwarden це роль організації: окремий командний простір, де паролі можна ділити між людьми, а не тримати кожен у своєму кутку.
Заходимо у веб-сейф за адресою сервера, реєструємо собі акаунт власника (поки SIGNUPS_ALLOWED=true це дозволяє) і далі: Password Manager -> New organization. Назвемо її, скажімо, «Наш офіс».
Усередині організації паролі розкладають по колекціях - це наче теки з доступом по відділах. Логічно завести щось на кшталт:
IT- сервери, панелі, домени;Бухгалтерія- банк-клієнти, податкові кабінети;Спільне- те, чим користуються всі.
Тепер запрошуємо команду: Members -> Invite user. На кожного вказуємо роль і доступ до потрібних колекцій. Ролей чотири, від найбільших прав до найменших: Owner, Admin, Manager, User. Більшості співробітників вистачить User із доступом до своїх колекцій.
Важливий нюанс саме для самостійного сервера. Коли людина прийме запрошення, власник або адмін мусить її ще й підтвердити (Confirm) - це звірка відбитка ключа, без неї учасник не зможе розшифрувати спільні записи. Ми не налаштовували SMTP, тому запрошення підтверджуються руками в інтерфейсі. Для 20 людей це робити вручну разів двадцять трохи втомливо, тож налаштування SMTP - перше, що варто додати згодом: тоді запрошення приходитимуть листом, а підтвердження спрощується.
Групи (Groups) у Vaultwarden теж є, але це поки beta-можливість - для старту надійніше роздавати доступ по колекціях напряму.

Підключаємо клієнти#
Застосунки ті самі, що й у Bitwarden - розширення для браузера, десктоп, мобільний. Єдине, що треба зробити інакше: до входу вказати застосунку, що сервер у нас власний. У налаштуваннях входу відкриваємо Settings -> Self-hosted (або поле Server URL) і вписуємо https://192.168.1.50. Аж тоді входимо - поштою й майстер-паролем.
Далі все як у звичайному Bitwarden: зберігаєш запис на одному пристрої - і за кілька секунд він з’являється на решті. Ось це «воно справді працює» найкраще один раз побачити:
Одне чесне застереження. Вхід без пароля через passkey (WebAuthn) хоче справжнє доменне ім’я й по голому IP не заведеться. Але це не біда: при вході по IP ми покладаємося на майстер-пароль плюс двофакторку через TOTP - і те, і те працює бездоганно.
Замикаємо двері#
Поки ми заводили акаунти, реєстрація була відкрита - і це дірка: будь-хто, хто дотягнеться до сервера, міг би собі завести акаунт. Коли команда на місці, реєстрацію треба закрити. У .env міняємо:
| |
і перезастосовуємо:
| |
Тепер нові акаунти заводяться тільки через запрошення адміном. Двері замкнені.
Бекап#
Усе цінне лежить у /mnt/data/vaultwarden - і найперше це база db.sqlite3 та ключ rsa_key.pem. Втратиш rsa_key.pem - і всіх розлогінить, доведеться входити наново. Жива база SQLite не любить, щоб її копіювали звичайним cp під час роботи, тому Vaultwarden має вбудований бекап, який знімає узгоджену копію:
| |
Цю папку варто щоночі відправляти на NAS чи в інше місце поза сервером. Детальніше розкладати бекап і відновлення будемо окремо - поки що головне, щоб нічна копія просто була.
Безпека: сторінка /admin#
Найчутливіше місце сервера - та сама сторінка /admin. Її прикриває argon2-токен, який ми згенерували, тож тримай ADMIN_TOKEN у надійному місці. Радикальніший варіант для параноїка: лишати ADMIN_TOKEN порожнім між сеансами адміністрування, а виставляти тільки тоді, коли реально заходиш в адмінку.
І згадаймо, навіщо ми виставляли LOG_FILE. По цьому логу (/data/vaultwarden.log) можна пустити fail2ban, щоб він банив тих, хто перебирає майстер-паролі - рівно так, як ми це робили для SSH у пості про fail2ban із серії «Захист сервера». Два сторожі на одному сервері чудово уживаються.
Куди далі#
Маємо власний сейф для паролів команди: один раз розгорнули, роздали доступи - і офіс більше не ганяє паролі в чатах. Наступні очевидні кроки, коли буде настрій: SMTP для запрошень листом, справжній домен замість IP (і Let’s Encrypt замість роздачі сертифіката), повноцінна схема бекапу.
Якщо ж уся ця історія з «зроби сам» - не твоє, і хочеться, щоб хтось просто розгорнув і наглядав за цим замість тебе, напиши в teneta.tech - підкажуть і візьмуть на себе.
А серію «Офіс без адміна» продовжимо: цеглинка за цеглинкою зберемо офісну інфраструктуру, яку потягне звичайна людина без системного адміністратора за плечима.



