kjoner106 downloadsSync your vault with your own self-hosted LiteSync server. End-to-end encryption, version history, and automatic three-way merge.
Private, self-hosted sync for Obsidian. Your notes sync through a lightweight server that you run yourself — end-to-end encrypted, with version history and automatic merge. No third-party cloud, no account, no telemetry.
Works on desktop and mobile (Obsidian 1.13+). 中文文档 / Chinese documentation →
Requires a LiteSync Server — a single Docker container you deploy with one command (runs happily on a 1-core / 256MB VPS): https://github.com/KJoner/litesync-server
Deploy the server — one line on your VPS, it prints your API token when done:
bash <(wget -qO- https://raw.githubusercontent.com/KJoner/litesync-server/master/scripts/litesync-install.sh)
Install LiteSync in Obsidian and open its settings
Fill in Server URL and API Token, hit Test Connection
If the remote vault is still empty, the test offers the next step right there: Close / Sync now / Add E2EE and sync now. On an empty vault there is no migration — the very first upload is already ciphertext, so your notes never reach the server in plaintext
Otherwise the onboarding wizard opens and asks how this device should join the existing remote vault
That's it — edits now sync automatically in the background.
You never type the server config twice:
The pairing package is encrypted on-device and expires in 5 minutes after a single use; the server only ever sees ciphertext. If your vault on the new device already has notes, the wizard offers a safe merge instead — nothing is ever silently overwritten, and nothing is ever permanently deleted.
data.json).http:// server URLs are rejected — credentials and notes
never travel over plain HTTP.#fragment, which browsers do not send to servers.One server can now host a few mutually invisible vaults (invite-only — registration stays closed). The admin generates one-time invite links in the web UI; the invitee picks a username and receives their own API token plus a step-by-step onboarding guide. Isolation is complete — files, change feeds, history, shares, devices and E2EE key documents are all partitioned per vault, enforced by an auto-generated cross-tenant test matrix. The admin can see usernames and usage, but cannot obtain anyone's API token (only hashes are stored) or E2EE password (never uploaded). A lost token cannot be recovered — by design there is no recovery flow to socially engineer.
If you suspect your API token leaked (but not your E2EE password), reset it
from the web UI's Account page: the old token dies immediately, all device
credentials of that account are revoked, and every device recovers by simply
pasting the new token and clicking "Test connection" — no re-onboarding, no
full re-sync, local data untouched. On E2EE-enabled vaults the reset endpoint
requires a credential derived from your encryption key, so an attacker
holding only the token cannot race you to the reset button. A reset cannot
retroactively recall ciphertext downloaded during the leak window — that part
is protected only by your E2EE passphrase strength. Emergency fallback:
obsync token reset on the server shell.
One API token can own several mutually isolated remote vaults. The onboarding wizard starts with a vault picker (pick an existing vault to restore/merge, or create a new one to initialize from this device); the picker shows even when you own a single vault, because it is the only entry point for creating another one. Initializing an empty vault requires E2EE — the first upload is already ciphertext, and there is no plaintext-first-migrate-later path (existing plaintext vaults are unaffected). The settings page shows which vault this device syncs to, with an in-place rename (display name only). Switching vaults is deliberately heavyweight — it re-runs the wizard, discards this device's sync ledger (local notes are kept) and reconciles from scratch. One E2EE passphrase may be reused across vaults, but every vault has its own key document and salt; device credentials stay bound to a single vault (a stolen device cannot move laterally into your other vaults). The web UI gets a vault switcher.
Being explicit about what LiteSync does not protect against is part of the design. The list below is accurate as of v0.17.0.
End-to-end encryption means the server cannot read your notes. It does not mean the server is trusted with everything else. Today the client detects and hard-fails on the attacks it can anchor locally:
| Attack | Detected? | How |
|---|---|---|
| Return someone else's content as your file | ✅ | fileId must match the object we asked for |
| Replay an older version of a file | ✅ | contentGeneration must not go backwards |
| Replay older metadata (e.g. undo a rename) | ✅ | authenticated metaGeneration must not go backwards |
| Serve two different metadata at the same generation | ✅ | metadata fingerprint mismatch = fork, sync stops |
| Downgrade the encryption envelope | ✅ | repository-wide envelope floor, envelopes only move up |
| Feed a path-traversal filename via crafted metadata | ✅ | decrypted paths are validated before touching disk |
| Roll the repository back to an old backup | ✅ | repoEpoch change forces an explicit recovery merge |
| Show device A one repository state and device B another | ✅ (v0.15) | device-signed checkpoints; a fork stops sync instead of picking a side |
| Roll back to an older repository state after you synced | ✅ (v0.15) | the trust anchor only moves forward, and every checkpoint must link to a chain you have seen |
| Withhold a file you have never seen | ❌ | there is no local anchor for something you never had, and no proof that the server handed you the complete set |
That last row is the honest, structural gap — not a missing feature. The accurate claim is:
A malicious server cannot roll back or swap content you have already synced without being detected, and cannot keep showing different repository states to different devices of yours. It can still refuse to serve you, and it can still hide a file you have never seen.
Signed checkpoints (v0.15) are signed by your devices, never by the server.
A new device does not trust the first manifest the server offers — it receives a
trusted anchor through device pairing, whose key travels only in the link's
#fragment.
End-to-end encryption hides content. It does not hide everything around it, and the optional privacy settings only reduce the resolution:
| Metadata | Default | With the optional settings on |
|---|---|---|
| Exact object size | visible | bucketed (worst case +12.5% storage) |
| Edit timestamps | per-save, near real time | which time window the edit fell into |
| Stored mtime | exact | rounded down to the configured granularity |
| Whether you edited at all in a window | visible | still visible — no cover traffic |
| Number of objects, access pattern | visible | visible |
| Paths and filenames | visible | pseudonyms only, if you enable the RC path encryption below |
Size padding covers the content envelope only; the metadata envelope (LSM1) and share-name envelope (LSN1) are not padded yet. Both are small and their lengths cluster tightly, but it is a known gap rather than a solved problem. Enabling padding does not rewrite existing files — they move to the padded envelope the next time you edit them.
Disabled by default. The migration is resumable and keeps deletion barriers intact, but the final erasure step is irreversible and any backup taken before the migration still contains plaintext paths. Do not enable it on a vault that has no copy.
Note.md and note.md are treated as the same file regardless of which
operating system you are on, and café.md written as NFC or NFD is likewise
one file. This is deliberate, and measurement backs it up: on real devices
iOS is case-sensitive and normalises Unicode, while Android is the
opposite — case-insensitive and non-normalising. Judging by the local
platform would make those two devices disagree about whether two names
collide, and they would then overwrite each other. The rule therefore takes
the strictest interpretation across all platforms. The cost is that you
cannot keep Note.md and note.md as separate files even on a system that
allows it; the benefit is that no device can silently overwrite another's.Officially supported and tested limits are documented in the server README: 20 000 files per vault, 100 MB per file, 90 days offline. Beyond those numbers things are not broken by design — they are simply untested.
Until LiteSync is available in Community Plugins:
main.js, manifest.json, styles.css from the
latest release<YourVault>/.obsidian/plugins/litesync/npm install
npm run build # type-check + bundle to main.js
npm run lint # eslint-plugin-obsidianmd recommended rules
npm test # 289 tests, discovered from tests/ (merge / crypto /
# pairing / state / crash points / adversarial protocol /
# real filesystem semantics)
npm run test:mobile # mobile CI: Node/Electron dependency audit + build + tests
npm run check:adr # every ADR is referenced from the code it governs
npm run check:inv # every invariant (INV-xx) has annotated tests
npm run dev # watch mode
CI runs the suite on Linux, macOS and Windows, because the file-system semantics this plugin depends on (case sensitivity, Unicode normalization, atomic replace) genuinely differ per platform. Mobile is covered by the in-plugin "Platform compatibility probe" command, since Obsidian Mobile cannot run in a CI runner.
Contributor-facing architecture notes live in AGENTS.md.
Releases are automated: pushing a tag that matches manifest.json's version
lints, builds, attests provenance, and drafts a GitHub Release with main.js,
manifest.json, and styles.css attached.