MAATRIX / Блог / Vaultwarden не синхронизируется: причины и решение

Vaultwarden не синхронизируется: причины и решение

Vaultwarden не синхронизируется: причины и решение

MAATRIX

Пароль сохранён в браузере на ноутбуке, а на телефоне его нет ни через минуту, ни через час. Или наоборот: расширение подхватывает всё мгновенно, а десктопный клиент показывает вчерашний сейф. Почти всегда данные на сервере уже лежат — сломан не обмен, а канал уведомлений, которым сервер говорит клиенту «сходи обновись». Разберём каналы по очереди, с командами проверки и точными текстами ошибок.

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

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

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

Как Vaultwarden синхронизируется на самом деле

Половину вопросов снимает одно: данные ходят только одним способом — клиент делает GET /api/sync?excludeDomains=true и забирает весь сейф целиком. Перед этим он спрашивает дешёвую метку: GET /api/accounts/revision-date возвращает голое число, Unix-время последнего изменения аккаунта в миллисекундах.

# access_token видно в devtools, во вкладке Network у любого запроса к /api
curl -s https://vault.example.com/api/accounts/revision-date \
     -H "Authorization: Bearer $TOKEN"
# 1756374764000

Это точка отсечки: число не выросло — сервер изменение не принял, и пляски с вебсокетами бесполезны; число растёт, а второй клиент не обновился — виноват канал уведомлений. Каналов «пинка» ровно два, и они не заменяют друг друга. WebSocket на /notifications/hub обслуживает веб-хранилище, десктоп и расширение браузера. Push-релей Bitwarden обслуживает iOS и Android: эти клиенты WebSocket не используют вообще, никогда, и включать его нужно отдельно.

Тест на тридцать секунд — обновить «сломанный» клиент вручную: «Settings → Sync → Sync vault now» в расширении и десктопе, потянуть список вниз в мобильном, перезагрузить страницу в вебе.

Что происходитГде искать
После ручного Sync данные появилисьКанал уведомлений: вебсокет или push
Ручной Sync ничего не меняет, ошибок нетИзменения не доехали до сервера
Ручной Sync выдаёт ошибку или разлогиниваетTLS, DOMAIN, часы
Видно не все записи организацииСтатус участника, а не синхронизация

На сервере откройте /adminDiagnostics: там сверяются часы сервера и браузера, DOMAIN с адресом захода, DNS, состояние WebSocket и доступность себя снаружи по HTTPS. Красная строка обычно и есть причина. Без авторизации работает curl -s https://vault.example.com/alive — вернёт время сервера строкой "2026-08-28T09:12:44.518Z" и докажет, что прокси доносит запросы до приложения.

WebSocket и /notifications/hub: чаще всего виноват старый конфиг

Начиная с версии 1.30.0 Vaultwarden отдаёт WebSocket через основной порт, на пути /notifications/hub. Отдельного порта 3012 больше нет, WEBSOCKET_ENABLED заменена на ENABLE_WEBSOCKET (по умолчанию true), WEBSOCKET_PORT игнорируется. Но в сети лежат мануалы 2021–2023 годов, где /notifications/hub проксируется на 127.0.0.1:3012 — на свежей сборке там никто не слушает. В консоли браузера (F12) это выглядит дословно так:

WebSocket connection to 'wss://vault.example.com/notifications/hub?access_token=eyJ...'
failed: Error during WebSocket handshake: Unexpected response code: 502
[SignalR] Error: Failed to start the connection: Error: WebSocket failed to connect.

Рабочий фрагмент для Nginx — оба location идут на один и тот же адрес:

map $http_upgrade $connection_upgrade { default upgrade; '' close; }

location / {
    proxy_pass http://127.0.0.1:8080;
    proxy_set_header Host            $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}

location /notifications/hub {
    proxy_pass http://127.0.0.1:8080;
    proxy_http_version 1.1;
    proxy_set_header Upgrade    $http_upgrade;
    proxy_set_header Connection $connection_upgrade;
    proxy_read_timeout 3600s;
}

Директива map обязана лежать в контексте http, а не внутри server, иначе nginx -t ответит "map" directive is not allowed here. Проверить апгрейд можно, не поднимая клиент:

curl -i -N -o /dev/null -D - \
  -H "Connection: Upgrade" -H "Upgrade: websocket" \
  -H "Sec-WebSocket-Version: 13" \
  -H "Sec-WebSocket-Key: $(openssl rand -base64 16)" \
  https://vault.example.com/notifications/hub 2>&1 | head -3

Ждём HTTP/1.1 101 Switching Protocols; токен мы не передавали, поэтому Vaultwarden тут же закроет соединение — важна только первая строка. 502 Bad Gateway — проксирование в несуществующий 3012. 400 или 200 — заголовки Upgrade и Connection не дошли, запрос обработал location /. Прочие грабли прокси — в статье про Nginx как реверс-прокси.

Caddy и Traefik апгрейд проксируют сами. За Cloudflare вебсокет разрешён на всех тарифах, но «Bot Fight Mode» и жёсткие правила WAF отдают клиенту challenge-страницу вместо JSON.

И то, что экономит часы отладки: заблокированный клиент уведомления не слушает. Расширение и десктоп рвут соединение при блокировке сейфа и подключаются заново после ввода мастер-пароля — ждать мгновенного обновления от залоченного клиента бессмысленно.

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

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

Развернуть Vaultwarden

Телефон не догоняет: мобильным нужен push-релей

Самая «непонятная» жалоба: в браузере всё мгновенно, а на телефоне записи появляются только после того, как потянешь список вниз. Так и должно быть, пока не настроен push: мобильные клиенты получают уведомления через сервис самой Bitwarden, и self-hosted сервер обязан там зарегистрироваться.

На bitwarden.com/host получаете Installation ID и Installation Key — ключ выдаётся один раз, сохраните сразу. Там же выбирается регион, US или EU, и он должен совпасть с адресами в конфиге:

PUSH_ENABLED=true
PUSH_INSTALLATION_ID=1f8bb2a4-0e5c-4d1b-9f77-2c0a1f5b7e33
PUSH_INSTALLATION_KEY=xxxxxxxxxxxxxxxxxxxx
# Только для региона Europe:
PUSH_RELAY_URI=https://push.bitwarden.eu
PUSH_IDENTITY_URI=https://identity.bitwarden.eu

По умолчанию используется американский релей (push.bitwarden.com, identity.bitwarden.com), при US-регистрации две последние строки не нужны. Перепутанный регион — типичная ошибка: ключ формально верный, но чужой релей его не признаёт.

После перезапуска смотрите журнал: docker logs --tail 200 vaultwarden 2>&1 | grep -i push. Запросы к identity.bitwarden.* с кодом 400 или 401 означают неверную пару ID/ключ либо не тот регион. Полная тишина при PUSH_ENABLED=true обычно значит, что исходящий 443 закрыт фаерволом — проверьте curl -sI https://push.bitwarden.eu.

Два подводных камня при верном конфиге:

  • Устройство регистрируется при входе. Телефоны, залогиненные до включения PUSH_ENABLED, токен не отправляли. На каждом нужно выйти из аккаунта и войти заново, иначе уведомления не придут никогда.
  • Экономия батареи на Android. Без разрешения «Unrestricted» система придерживает уведомления, и синхронизация выглядит случайной. На оболочках Xiaomi, Samsung и Huawei ограничение включено по умолчанию.

Честно про приватность: релей — третья сторона. Через него уходит идентификатор устройства и факт изменения сейфа; содержимое записей не передаётся и не может — оно зашифровано ключом, которого у Bitwarden нет. Но требование «сервер не общается с внешними сервисами вообще» означает push выключенным и ручное обновление телефона. Это осознанный размен, а не поломка.

И регулярное: адрес сервера в приложении задаётся до входа, через шестерёнку на экране логина. Заполняйте только «Server URL» — опечатка в отдельных полях «API URL» и «Identity URL» даёт успешный вход и мёртвую синхронизацию.

Клиент не доходит до сервера: сертификат, DOMAIN и часы

Симптомы этой группы громче: ошибка при входе, вылет из аккаунта, 401 на каждом обновлении.

Сертификат. Клиенты Bitwarden требуют TLS, доверенный операционной системой. Самоподписанный мобильное приложение не примет: на Android в деталях ошибки будет Trust anchor for certification path not found, на iOS — The certificate for this server is invalid. Ещё коварнее неполная цепочка: настольные браузеры докачивают промежуточный сертификат сами, а телефон нет. Отсюда классика «на компьютере синхронизируется, на телефоне нет».

echo | openssl s_client -connect vault.example.com:443 \
     -servername vault.example.com 2>/dev/null | grep 'Verify return code'

Verify return code: 21 или unable to get local issuer certificate в ответе curl — цепочка неполная: в Nginx должен быть указан fullchain.pem, а не cert.pem. Выпуск и автопродление — Let's Encrypt SSL на VPS.

Переменная DOMAIN. Полный адрес со схемой и без слеша на конце: DOMAIN=https://vault.example.com. От неё зависит RP ID для двухфакторной аутентификации по WebAuthn: несовпадение ломает вход по аппаратному ключу, а без входа нет токена — и клиент честно показывает «не синхронизируется».

Часы. Vaultwarden выдаёт JWT с ограниченным сроком жизни, и расхождение времени ломает обновление токена: клиент ловит 401 на /api/sync и вылетает из аккаунта.

curl -s https://vault.example.com/alive; echo; date -u +%Y-%m-%dT%H:%M:%SZ
timedatectl status | grep -E 'System clock|NTP service'

Расхождение больше пары минут лечится командой timedatectl set-ntp true либо установкой chrony с проверкой chronyc tracking. Контейнер берёт время у хоста — чинить надо на хосте. Оговорка: на OpenVZ и LXC часы задаёт нода провайдера, своим NTP их не поправить, и регулярный уход времени там повод переехать на KVM.

Изменения не доезжают до сервера: revision-date стоит на месте

Если ручной Sync не помогает, а число из /api/accounts/revision-date после сохранения записи не выросло, канал уведомлений ни при чём. Клиент показывает лаконичное An error has occurred, подробности — только в журнале сервера.

База заблокирована. Vaultwarden по умолчанию живёт на SQLite: /data/db.sqlite3 плюс db.sqlite3-wal и db.sqlite3-shm. Если /data смонтирован по NFS, SMB или sshfs, файловые блокировки работают неверно и запись падает с DatabaseError(Unknown, "database is locked") — держите данные на локальном NVMe. При десятке активных пользователей поднимите DATABASE_MAX_CONNS=20 и DATABASE_TIMEOUT=60: утренний вход всей команды даёт всплеск параллельных /api/sync. Прочие ошибки каталога /data — в материале про частые ошибки Vaultwarden.

Кэширующий CDN. Обратная ситуация: сервер запись принял, revision-date растёт, а клиент видит старое. Cloudflare с правилом «Cache Everything» отдаёт ответ из кэша, и клиент получает вчерашний сейф с кодом 200.

curl -sI https://vault.example.com/api/accounts/revision-date | grep -i cf-cache

Заголовок cf-cache-status: HIT — приговор; лечится правилом Bypass Cache для /api/*, /identity/* и /notifications/*. То же бывает на корпоративном прокси с агрессивным кэшем.

Для разбора инцидента временно включите LOG_LEVEL=debug и LOG_FILE=/data/vaultwarden.log. Предупреждение: в debug-журнал попадают адреса почты пользователей — после диагностики верните info и удалите файл.

Всё работает, но не у всех: организации и старые сессии

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

Приглашение не подтверждено. У участника организации три состояния: InvitedAcceptedConfirmed. Человек принял приглашение и остановился на Accepted — администратор обязан нажать Confirm в списке участников. До этого пользователь не получает ключ организации, и записи коллекций для него физически нерасшифровываемы. Пересинхронизация не поможет: сначала подтверждение, потом Sync.

Приглашения не уходят. При SIGNUPS_ALLOWED=false и INVITATIONS_ALLOWED=true письмо шлёт сам Vaultwarden, и без настроенного SMTP (SMTP_HOST, SMTP_FROM, SMTP_USERNAME, SMTP_PASSWORD, SMTP_SECURITY=starttls) приглашение просто не создаётся. Кнопка тестового письма на /admin показывает дословную ошибку SMTP.

Смена мастер-пароля. Остальные сессии деавторизуются, и до нового входа устройство показывает старый локальный кэш — снаружи неотличимо от «перестало синхронизироваться». После ротации ключа шифрования владельцу придётся заново подтвердить всех участников организации.

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

Vaultwarden написан на Rust и нетребователен всерьёз: в простое процесс держит примерно 35–70 МБ RSS, всплеск даёт только массовый /api/sync при одновременном утреннем входе. Ресурсы съедают соседи — Nginx, certbot, Docker, бэкапы, — а не сам сейф.

Минимум честно рабочий: 1 vCPU, 1 ГБ RAM, 20 ГБ NVMe. Хватает семье или команде из 3–5 человек на SQLite, и ограничивает тут место под вложения, а не память. На 512 МБ Vaultwarden запустится, но обновление контейнера уронит его по OOM — в dmesg -T | grep -i oom будет Out of memory: Killed process ... vaultwarden. Гигабайт снимает риск целиком.

Комфортный вариант: 2 vCPU, 4 ГБ RAM, 40–60 ГБ NVMe. Помещается всё сразу: Vaultwarden, реверс-прокси с TLS, PostgreSQL вместо SQLite (он убирает весь класс блокировок из пятого раздела), ежедневные снимки базы и запас под вложения. Это рабочая конфигурация на 10–50 человек: перезапуск после обновления проходит незаметно, узкое место — диск и параллельные Sync. Организации с внешней СУБД и мониторингом закладывайте 4–8 ГБ: один PostgreSQL просит около гигабайта сверху.

Локация — Великобритания, Лондон. Для менеджера паролей это удачный компромисс: RTT из Москвы до лондонских площадок обычно укладывается в 45–70 мс, из ЕС — единицы миллисекунд, а обмен маленький, так что интерфейс ощущается мгновенным. Британский узел удобен коротким маршрутом до релея push.bitwarden.eu, стабильной валидацией Let's Encrypt и юрисдикцией внутри GDPR-контура. Если все пользователи и обязательства по 152-ФЗ в России — берите RU-локацию: пинг минимальный, а push продолжит работать, он ходит обычным HTTPS.

Ставить руками ничего не придётся: Vaultwarden есть в каталоге apps.maatrix.io и разворачивается автоматически при заказе — на Ubuntu и Debian. Доступы появляются в личном кабинете, в разделе «Доступ». Вам остаётся направить домен на выданный IP, выпустить сертификат, прописать DOMAIN и заглянуть на /admin/diagnostics. Дальше закройте лишнее (ufw allow 22/tcp, ufw allow 443/tcp, ufw deny 8080/tcp), выставьте SIGNUPS_ALLOWED=false и настройте резервное копирование сейфа: база паролей без бэкапа опаснее её отсутствия. Установка с нуля — Vaultwarden на VPS. Оплата картой российского банка, по СБП, криптой или токеном MAAT.

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

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

Развернуть Vaultwarden

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

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

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

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

Обязательно ли включать WebSocket, чтобы Vaultwarden работал?

Нет. Без /notifications/hub сейф полностью функционален, изменения просто приезжают при разблокировке клиента или ручном Sync. Сокет — удобство, а не условие работоспособности.

Почему на компьютере всё синхронизируется, а на телефоне нет?

Либо не настроен push-релей (PUSH_ENABLED, Installation ID и Key с bitwarden.com/host) — мобильные клиенты WebSocket не используют вовсе; либо у сертификата неполная цепочка, которую браузер докачивает сам, а телефон нет.

Как понять, что изменение вообще дошло до сервера?

Запросить GET /api/accounts/revision-date с токеном до и после сохранения записи. Число выросло — сервер принял данные, чинить надо канал уведомлений; не выросло — смотрите журнал Vaultwarden и раздел про SQLite.

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

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