Skip to contentMenu Download OpenVireo

This page is for running the sync server behind an OpenVireo account server (openvireo-auth), as the hosted service does. A self-hosted server needs none of it.

With an account server, clients authenticate with short-lived access tokens (JWT, signed with Ed25519) instead of device tokens, and databases become vaults: one encrypted .kdbx per vault, shared by everyone the account server grants access to. The sync server never decides who may open a vault; it only checks what the token says. Sharing, organisations, key rotation and recovery all happen on the account server and in the clients.

Modes

SYNC_AUTH_MODEAccepted tokensRoutes
standalone (default)device tokensdatabases/*, devices/pairing*, events
jwtaccess tokensvaults, vaults/*, events, internal/*
botheither; a token with two . is an access tokenall of the above

Routes a mode doesn’t have answer 404. In both mode, an access token on a legacy route, or a device token on a vault route, gets 403. both is what the hosted service runs, so people who started with device tokens can move to an account without losing their databases (see below).

Settings

VariableDefaultMeaning
SYNC_AUTH_JWKS_URL(required for jwt/both)The account server’s /.well-known/jwks.json
SYNC_AUTH_ISSUER(required for jwt/both)Its issuer URL, the tokens’ iss (as in its /.well-known/openvireo-configuration)
SYNC_AUTH_AUDIENCEov-syncThe audience every token’s aud must contain
SYNC_INTERNAL_TOKEN(none)Shared secret the account server uses for the internal routes. Unset: those routes are off (404)

For the hosted setup:

SYNC_AUTH_MODE=both \
SYNC_AUTH_JWKS_URL=https://accounts.example.org/.well-known/jwks.json \
SYNC_AUTH_ISSUER=https://accounts.example.org \
SYNC_INTERNAL_TOKEN=<random secret shared with the account server> \
SYNC_ALLOWED_ORIGINS=https://app.example.org \
openvireo-sync-server

Generate the internal token with something like openssl rand -base64 32 and give the account server the same value. Anyone who has it can push access events and delete vaults, so keep it out of the clients.

Access tokens

The server accepts only EdDSA tokens and checks the signature, iss, aud, exp and nbf (with 60 seconds of clock skew). It fetches the JWKS on first use and caches it, fetches it again when a token names a key id it doesn’t know (at most once every 30 seconds) and refreshes it hourly, so a key rollover on the account server needs no restart.

The token’s vlt claim lists the vaults it opens and the role for each:

RoleAllows
readlist, download, history
writealso update an existing vault (PUT with If-Match)
managealso create (PUT with If-None-Match: *) and delete

A vault the token doesn’t list gets 403, whether it exists or not, so a token can’t probe for other vaults. The token’s sub (user:<uuid> or sa:<uuid> for a service account) decides which access events its events stream receives.

Access changes

When the account server grants, revokes or rotates access, it tells the sync server with POST /api/v1/internal/notify, and every events stream of the affected subjects gets an access event. The clients then fetch a fresh access token (with the new vlt) and reconnect. A stream also ends once its token’s exp has passed, so a revoked grant stops working within one token lifetime even if the event is lost.

When a vault must go without any client deleting it, for example once an account recovery archive expires, the account server sends DELETE /api/v1/internal/vaults/{vault}.

With Postgres, both reach streams on every instance (through NOTIFY).

Moving a database into a vault

To turn a user’s database into a vault (for a user moving from a device token to an account), take the vault id from the account server and run

openvireo-sync-server migrate-to-vaults <user> <database-id> <vault-uuid>

It copies the database with its kept versions, revisions and ETags to vaults/<vault-uuid> and then deletes the user’s copy (devices of that user see it deleted). It refuses when the vault already exists and leaves everything as it was if the database changes while it is copied. It works with either storage; with the data directory, stop the server while it runs.

Clients that move a database themselves (OpenVireo’s and the web app’s Move to account) don’t need this: they upload a re-keyed copy as a new vault.

Storage of vaults

Vaults never share a storage key with a user’s databases: in the data directory they live in vaults/<uuid>.kdbx (next to databases/), with history in vaults/.history/<uuid>/; with Postgres in the tables vaults and vault_versions, with files under _vaults/<uuid>/ in the bucket, a prefix no user name can have. import <data-dir> also copies a data directory’s vaults.

The Sync Server docs cover OpenVireo Sync Server, develop (0d4f752), last updated 26 October 2026. Found a mistake? Edit this page on GitHub.