MAATRIX / Блог / Vaultwarden на сервере: частые ошибки и решения

Vaultwarden на сервере: частые ошибки и решения

Vaultwarden на сервере: частые ошибки и решения

MAATRIX

Хранилище паролей ломается всегда не вовремя: утром расширение молча отдаёт Failed to fetch, панель /admin уверяет, что она отключена, а контейнер бесконечно перезапускается. Хорошая новость — стек у Vaultwarden короткий, и почти каждая поломка садится ровно на один стык. Разберём типовые ошибки по слоям: с дословными текстами из логов, командами проверки и честными оговорками там, где чинить уже нечего.

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →

Триаж за две минуты: где именно сломалось

Путь запроса выглядит так: клиент → DNS и TLS → обратный прокси → контейнер Vaultwarden → SQLite на диске. Диагностика — это отсечение слоёв, а не перебор гайдов.

Что видит пользовательВероятный слойПервая проверка
ERR_CONNECTION_REFUSEDконтейнер или портdocker ps
502 Bad Gatewayпрокси ↔ контейнерcurl на /alive мимо прокси
Вход прошёл, дальше An error has occurredверсии сервера и клиента/api/config
/admin пишет, что панель отключенапеременные не доехалиdocker exec ... printenv
В расширении Failed to fetchTLS-цепочкаopenssl s_client
Открывается, но изменения не долетаютвебсокеты и pushотдельный разбор

Три команды, которые сужают поиск быстрее любого чтения логов:

docker inspect -f 'status={{.State.Status}} restarts={{.State.Restarts}} oom={{.State.OOMKilled}}' vaultwarden
curl -s -o /dev/null -w '%{http_code} %{time_total}\n' http://127.0.0.1:8080/alive
curl -s https://vault.example.com/api/config | jq '.server, .version'

/alive — единственный эндпоинт без авторизации, он возвращает текущее время UTC. Обращение по 127.0.0.1 намеренно минует Nginx: 200 изнутри и 502 снаружи означают, что Vaultwarden жив, а виноват прокси, и читать надо /var/log/nginx/error.log. На локальной петле здоровый ответ укладывается в 5–10 мс; выросший до секунд time_total — сервер упирается в диск или память.

Растущий счётчик restarts — это рестарт-луп, и логи текущего запуска бесполезны: процесс не дожил до интересного. Смотрите docker logs --tail 50 --timestamps vaultwarden. Успешный старт всегда заканчивается одинаково:

[INFO] Rocket has launched from http://0.0.0.0:80

Нет этой строки — до обслуживания HTTP дело не дошло, и раздел про прокси можно пропустить.

Контейнер падает по кругу: права, порт, память и пропавший том

Три текста в логе покрывают почти все случаи, когда Vaultwarden не поднимается.

Error: unable to open database file или attempt to write a readonly database. Права на каталог данных. Тонкость, из-за которой правку делают наполовину: SQLite нужен доступ на запись не только к db.sqlite3, но и к самому каталогу — рядом создаются db.sqlite3-wal и db.sqlite3-shm. Сверяйте владельца с UID процесса внутри контейнера:

docker exec vaultwarden id
stat -c '%U:%G %a' /opt/vaultwarden/vw-data
chown -R 1000:1000 /opt/vaultwarden/vw-data && chmod 750 /opt/vaultwarden/vw-data

По умолчанию образ работает от root, и проблема появляется после того, как в compose добавили user: "1000:1000", а владельца каталога не поменяли.

failed to bind port 0.0.0.0:8080/tcp: bind: address already in use. Порт занят — чаще всего забытым вторым контейнером; виновника показывает ss -lntp | grep :8080. Помните, что внутри контейнера Vaultwarden слушает 80-й порт: меняется он переменной ROCKET_PORT, правка левой части ports: на это не влияет.

oom=true в выводе docker inspect. Процесс убит по нехватке памяти. Сам Vaultwarden в простое держится в районе 40–60 МБ, но проверка Argon2-хеша при входе в /admin с пресетом bitwarden требует 64 МБ единовременно на каждую попытку — на машине с 512 МБ без свопа этого достаточно.

Отдельный, самый болезненный случай: контейнер работает, а сейф пустой. Почти всегда это отсутствующий том — данные писались в слой контейнера и исчезли при пересоздании.

docker inspect -f '{{range .Mounts}}{{.Source}} -> {{.Destination}}{{println}}{{end}}' vaultwarden

Строки с -> /data нет — тома нет. Если старый контейнер ещё существует (docker ps -a), данные вытаскиваются через docker cp vaultwarden_old:/data ./rescue, и делать это надо до любых docker compose down. Честно: если контейнер запускали с --rm или уже снесли командой docker compose down -v (флаг -v удаляет именованные тома), восстанавливать нечего. Общая механика падений — в статье про контейнер, который сразу падает.

Развернуть за пару минут

Готовый образ на VPS MAATRIX: NVMe, AMD EPYC, root-доступ. Локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Развернуть Vaultwarden

Версии разъехались: сервер старее клиентов

Главная новая ошибка 2026 года, и на ошибку она не похожа. Vaultwarden — не Bitwarden, а независимая реализация его API. Клиенты (расширение, десктоп, мобильное приложение) обновляются сами и без спроса, сервер стоит на закреплённом теге годами. В какой-то момент клиент стучится в эндпоинт, которого в вашей сборке ещё нет, и получает 404.

Выглядит так: вход проходит, дальше клиент показывает An error has occurred, сейф открывается пустым или часть записей не расшифровывается. Данные целы — их просто некому отдать в новом формате.

Разработчик публикует соответствие явно, его стоит держать под рукой:

Версия VaultwardenПод какие клиентыКогда вышла
1.36.0web-vault 2026.4.1, клиенты до 2026.6весна 2026
1.37.0клиенты 2026.7.0+июль 2026
1.37.2клиенты 2026.8.0+22 августа 2026

Проверка занимает полминуты: docker exec vaultwarden /vaultwarden --version — версия сервера, curl -s https://vault.example.com/api/config | jq -r '.server.version' — то же снаружи, версия клиента лежит внизу страницы настроек расширения. Расхождение поколений и есть диагноз.

Неприятная честность: откат назад не поддерживается. При старте новая версия прогоняет миграции схемы SQLite, и они односторонние. Порядок обновления поэтому ровно такой:

sqlite3 /opt/vaultwarden/vw-data/db.sqlite3 ".backup '/root/vw-$(date +%F).sqlite3'"
sed -i 's|vaultwarden/server:1.36.0|vaultwarden/server:1.37.2|' docker-compose.yml
docker compose pull && docker compose up -d
docker compose logs -f --tail=50 vaultwarden

Первая строка обязательна: если миграция упадёт на середине, вернуть базу можно только из копии, снятой правильным способом (бэкап и восстановление). Веб-хранилище вшито в образ и руками не обновляется. Обе стратегии тегов ломаются, просто по-разному: latest однажды приедет с изменением, ломающим конфиг посреди рабочего дня, а закреплённый тег однажды отстанет от клиентов. Компромисс — фиксированный тег плюс окно обновления раз в месяц.

Настройка «не применяется»: переменная не доехала до процесса

Классика: вы правите docker-compose.yml, перезапускаете, ошибок нет — и ничего не меняется. Причин ровно три, и все проверяются изнутри контейнера, а не в редакторе.

Первая: переменной у процесса нет. Смотрите не свой шелл и не файл, а окружение работающего контейнера.

docker exec vaultwarden printenv | sort | grep -E 'DOMAIN|SIGNUPS|ADMIN|SMTP'

Пусто там, где ожидалось значение, — env_file не подхватился или указан не тот путь. Отдельная ловушка Compose: правка файла, на который ссылается env_file, не всегда приводит к пересозданию контейнера. Привыкайте к docker compose up -d --force-recreate.

Вторая: опечатка в имени. Vaultwarden читает только известные ему имена и не ругается на незнакомые. Строка SIGNUP_ALLOWED=false вместо SIGNUPS_ALLOWED=false не даст ни ошибки, ни предупреждения — регистрация останется открытой, и через неделю в базе появятся чужие учётки. То же с префиксами: сервис ждёт ADMIN_TOKEN, а не VAULTWARDEN_ADMIN_TOKEN.

Третья: /data/config.json перекрывает переменные. Если вы хоть раз нажали «Сохранить» в веб-панели, настройки легли в файл, и с этого момента окружение проигрывает. Сервис честно предупреждает об этом при старте:

[WARN] The following environment variables are being overridden by the config.json file
[WARN] Please use the admin panel to make changes to them

Дальше идёт список имён. Решение — выбрать один источник: либо cat vw-data/config.json показывает пустой {} и всё живёт в переменных, либо всё настраивается панелью. Про первичную настройку — в статье как установить и настроить Vaultwarden на VPS.

Отсюда же растут две частые жалобы на панель. Страница /admin отвечает фразой The admin panel is disabled, please configure the 'ADMIN_TOKEN' variable to enable it — значит переменной у процесса нет, смотрите пункт первый. А Invalid admin token при заведомо правильном пароле проверяется одной командой:

docker exec vaultwarden sh -c 'echo "[$ADMIN_TOKEN]"'

Значение должно начинаться с $argon2id$ и целиком помещаться между скобками. Начинается с $argon2id — Compose или Portainer доэкранировал доллары; видны кавычки — они уехали в значение. И банальная, но регулярная причина: в форму вводят сам хеш вместо пароля, из которого он сделан. А 429 Too Many Requests на /admin — не поломка, а сработавший ADMIN_RATELIMIT_SECONDS: счётчик живёт в памяти и обнуляется перезапуском.

Виноват прокси, а не Vaultwarden: белая страница, TLS и подменённый IP

Если /alive на 127.0.0.1 отвечает 200, а снаружи всё плохо — дальше про Nginx.

Failed to fetch в расширении и «нет доверия» в мобильном приложении. Клиенты Bitwarden не работают с самоподписанным сертификатом и с неполной цепочкой. Браузер такую цепочку часто достраивает сам и показывает замочек, а расширение — нет; отсюда «в браузере работает, в расширении нет».

openssl s_client -connect vault.example.com:443 -servername vault.example.com </dev/null 2>&1 | grep -E 'Verify return code|depth='

Нужен Verify return code: 0 (ok) и минимум две ступени depth. В конфиге Nginx должен стоять fullchain.pem, а не cert.pem.

Белая страница веб-хранилища при рабочем /alive. Vaultwarden отдаёт собственный Content-Security-Policy, рассчитанный на встроенный web-vault. Добавленный «для безопасности» на прокси add_header Content-Security-Policy "default-src 'self'"; ломает интерфейс насмерть — в консоли браузера появится Refused to execute inline script because it violates the following Content Security Policy directive. Убирайте чужие CSP и X-Frame-Options с этого хоста; тот же эффект дают Rocket Loader и автоминификация JS у Cloudflare. Прочие грабли проксирования — в разборе Nginx как реверс-прокси.

В логах и банах фигурирует адрес прокси. Vaultwarden берёт адрес клиента из заголовка, имя которого задаёт IP_HEADER (по умолчанию X-Real-IP). Не передали заголовок — во всех попытках входа в журнале будет 172.18.0.1, и fail2ban либо не банит никого, либо банит шлюз Docker и выносит всех разом. Лечится строкой proxy_set_header X-Real-IP $remote_addr; в Nginx, а за Cloudflare — IP_HEADER=CF-Connecting-IP.

И мелочь, которую регулярно принимают за поломку: иконки сайтов Vaultwarden тянет сам, наружу — без исходящего HTTP их не будет, а ICON_BLACKLIST_NON_GLOBAL_IPS=true по умолчанию отсекает внутренние адреса. Это косметика, на работу хранилища не влияет.

Вход и 2FA: неверный код, часы и потерянный доступ

Invalid TOTP code при заведомо правильном коде. Код действует 30 секунд, и допустимый разбег — примерно один шаг. Расхождение часов на минуту уже даёт стабильный отказ, а контейнер берёт время у хоста, так что чинить надо систему:

timedatectl                      # ждём "System clock synchronized: yes"
timedatectl set-ntp true
chronyc tracking | grep -E 'System time|Leap'

Типичный сценарий — виртуалка, поднятая из снапшота или мигрировавшая между гипервизорами: часы уезжают на минуты. В журнале Vaultwarden при этом мелькают предупреждения про time drift.

Пасскей или ключ безопасности перестал работать. В консоли браузера — SecurityError: The relying party ID is not a registrable domain suffix of, nor equal to the current domain. WebAuthn привязан к домену из переменной DOMAIN, и тот обязан совпадать с адресом в строке браузера дословно — со схемой и без завершающего слэша. Сменили домен или ходите по IP: ключ придётся регистрировать заново, перенести его нельзя.

Потеряли второй фактор. Через панель: /admin → пользователь → снять двухфакторную аутентификацию. Без панели — только напрямую в базе, с копией и остановленным контейнером:

docker compose stop vaultwarden
sqlite3 /opt/vaultwarden/vw-data/db.sqlite3 \
  "DELETE FROM twofactor WHERE user_uuid=(SELECT uuid FROM users WHERE email='user@example.com');"
docker compose start vaultwarden

«Разлогинило всех разом». После восстановления из бэкапа или переноса клиенты требуют повторного входа, а в логе идут ошибки вида Invalid claim. Причина — потерянные ключи подписи токенов (rsa_key.pem и соседние файлы в /data): без них старые сессии недействительны. Это не потеря данных, а следствие неполной копии: в каталоге данных нужны не только база и вложения.

Ещё два симптома, похожих на поломку входа. Ответ 429 после серии попыток — это LOGIN_RATELIMIT_SECONDS и LOGIN_RATELIMIT_MAX_BURST, они работают как задумано. А Registration not allowed or user already exists при попытке завести коллегу означает SIGNUPS_ALLOWED=false: приглашайте через организацию с INVITATIONS_ALLOWED=true и настроенным SMTP, а не открывайте регистрацию заново.

Какой сервер под Vaultwarden брать в MAATRIX

Половина ошибок из этой статьи — не про Vaultwarden, а про то, как его ставили: тома, права на каталог, занятые порты, переменные, не доехавшие до процесса. Приложения из каталога apps.maatrix.io разворачиваются на сервере автоматически при заказе, и эти грабли туда не попадают: том смонтирован, владелец каталога согласован с процессом, порт свободен, переменные лежат в отдельном файле окружения. Автоустановка работает на Ubuntu и Debian, доступы появляются в личном кабинете, в разделе «Доступ». Команды выше нужны для диагностики, а не для установки.

Минимум: 1 vCPU, 1 ГБ RAM, 20 ГБ NVMe. Хватает команде до двух-трёх десятков человек: контейнер держится в районе 40–60 МБ, ещё 150–200 МБ уходит на демон Docker и Nginx. Ограничения честные и прямо связаны с ошибками выше. Своп на 1–2 ГБ включайте сразу, иначе oom=true из второй секции поймается на первой же серии попыток входа в /admin. И следите за диском: каждое обновление оставляет старый образ, а на заполненном диске SQLite падает с database or disk is full. Привычка — docker image prune -f после успешного обновления.

Комфортный вариант: 2 vCPU, 2–4 ГБ RAM, 40–60 ГБ NVMe. Разница не в скорости входа, она не изменится, а в том, что помещается рядом: копии базы за месяц, место под резервную копию перед обновлением, fail2ban, Uptime Kuma и вторая копия контейнера на другом порту — проверить новую версию до того, как её увидят пользователи.

Локация — Великобритания, Лондон. Скорость здесь ни при чём: синхронизация хранилища — килобайты JSON, лишние 30 мс не заметит никто. Аргументы другие: европейская юрисдикция и соседство с GDPR, чистый IP без чужой репутации, прямой доступ к европейскому push-ретранслятору bitwarden.eu. RTT из Москвы — 45–55 мс, из Берлина и Амстердама единицы миллисекунд. Если в хранилище персональные данные российских сотрудников и вы под 152-ФЗ, берите российскую локацию. Оплата — картами российских банков, по СБП, криптовалютой или токеном MAAT.

Трезвая оговорка: автоустановка снимает ошибки развёртывания, но не отменяет вашей части — домен и сертификат, закрытие регистрации, копия базы на другую машину и своевременное обновление под новые клиенты. Эти четыре пункта дают почти все инциденты, которые потом приходится разбирать.

Развернуть за пару минут

Готовый образ на VPS MAATRIX: NVMe, AMD EPYC, root-доступ. Локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Развернуть Vaultwarden

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →

Частые вопросы

Обновил контейнер, и клиенты начали отдавать «An error has occurred». Откатиться на прошлый тег?

Не выйдет: миграции схемы базы односторонние, и откат после них не поддерживается. Сравните версии через curl -s https://vault.example.com/api/config | jq -r '.server.version' и версию расширения — скорее всего, двигаться надо вперёд, к сборке под ваши клиенты (1.37.2 для клиентов 2026.8.0+), а не назад.

Правлю переменные, перезапускаю — ничего не меняется. Где смотреть?

По порядку: docker exec vaultwarden printenv | grep ИМЯ (переменная вообще доехала?), затем опечатка в имени — незнакомые имена Vaultwarden молча игнорирует, затем cat vw-data/config.json — сохранённые панелью настройки перекрывают окружение. И пересоздавайте контейнер через docker compose up -d --force-recreate.

Как за минуту понять, кто виноват — Vaultwarden или Nginx?

Постучитесь в контейнер напрямую, минуя прокси: curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8080/alive. Код 200 изнутри при ошибке снаружи означает, что сервис исправен и разбираться надо с прокси и сертификатом.

Нужны сами нейросети для контента?

Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.