Self Hosted Private Sync

by Samuel Naranjo
5
4
3
2
1
Score: 48/100

Description

Sync Obsidian vaults across devices with end-to-end encryption using a self-hosted server and a lightweight Obsidian plugin. Run a dependency-free Rust server with a built-in dashboard, pair devices securely, and keep all data encrypted client-side so decryption keys never leave your devices.

Reviews

No reviews yet.

Stats

5
stars
306
downloads
0
forks
33
days
2
days
6
days
79
total PRs
4
open PRs
33
closed PRs
42
merged PRs
274
total issues
26
open issues
248
closed issues
42
commits

Latest Version

7 days ago

Changelog

obsync 1.1.7

What changed

Fixed

  • Two devices typing in one note, on the same line or at the same spot, now combine letter by letter: every keystroke of both stays in the note, the same text typed at the same spot on both at once appears once, deleted text stays deleted, and text goes to a conflict copy only when the two versions' shared history cannot be read in full (below). Incoming text reaches an open editor as only the other device's change, so the cursor stays where you type and undo takes back only your own typing. A plugin rewriting a note right after a sync, on a line another device also changed, is still held rather than joined, even where the two rewrites changed different characters of that line: two times stamped on two devices no longer combine into a time neither wrote. Encryption, the wire format and the server are unchanged (#339).

  • Text deleted on one device no longer comes back from another. When a combined result came out exactly as the other device's text, that device's version was taken as is, and a deletion made here could come back for a moment, or for good when it had not been sent yet. The combined version is now sent instead, and an unsent deletion goes out first, even one made while the keystroke before it was still uploading (#339).

  • Two people typing in one note for minutes no longer get a notice that obsync "stopped renaming" it when nobody renamed it. A device that really keeps giving a note a different name is still stopped and named (#339).

  • On an iPhone or iPad, text from another device that arrives inside the word you are typing no longer makes your next key replace the letter before the cursor. That text now appears as soon as you pause, finish the word or move the cursor away (#339).

  • Saved editor input can sync while typing continues. Incoming edits merge with acknowledged versions while newer local input stays queued as a child version. Save confirmation, unfinished composition and recent human input now have separate checks instead of sharing a ten-second delay (#325).

  • Desktop editor refresh advances the native saved baseline before queued watcher reloads, preserving keystrokes and avoiding repeated external-change notices. Both the staged download and the editor's subsequent save are flushed before the write completes. Unsafe or unsupported editor states retain the native refresh fallback.

  • Incoming parent snapshots prepare while native saving continues; a save in flight no longer abandons the publication turn and restarts that work. The base and parents share one bounded authenticated chunk request, with each input independently verified. Current editor and disk checks still guard the final write.

  • Continuing to type before a just-delivered remote addition keeps fast saving active. Refresh confirmation no longer mistakes the already displayed addition for text that still needs to be merged.

  • Confirmed local saves and remote refreshes update live-preview consumers, including the native word and character counts, without another save after callbacks.

  • Live version history, including own echoes and superseded peer edits, stays in the existing bounded ancestry cache so long typing bursts do not spend historical-read budgets fetching records the device already received.

  • Confirmed saved snapshots avoid a second mobile read that races subsequent typing. Obsolete concurrent edits can skip redundant disk and head checks when the served feed names the locally held version as a head or cached ancestry proves the obsolete edit cannot advance it. Known dirty editors refuse readiness before queuing another mobile read. Linear replay, moves, deletions, rewrite controls and uncertain ancestry retain their checks.

  • Sync status uses a static icon, avoiding continuous SVG animation that delays native mobile file callbacks during typing.

  • Ordinary merges remember the exact result of their authenticated parents, as saved-editor merges do, without enlarging history or memory budgets.

  • History a device recalls or reads for a merge is put back in newest-first order before a base is chosen. Kept in the order it was reached, an older version could be taken as the newest, and keys typed where two people were typing left the note on both devices for a moment (#339).

  • The other device's typing no longer appears twice, with a letter it deleted back, when you type the moment it arrives. A save landing just after its text appeared was taken for a replaced file: the text stayed in the note, but went out again as your own typing (#339).

  • Push reconciliation reuses the head snapshot it just fetched when that snapshot contains both inputs, avoiding a second request that can chase a continuously advancing peer. Unrelated or incomplete snapshots still require a fresh graph.

  • A live editor reaching the merge-history work limit waits for the peer's next merge instead of classifying the limit as overlapping text. Other notes continue syncing; the existing bounded retry and cold-history fallback remain.

  • Two versions whose shared history could not be read in full -- past the merge-history work limit, or with a retained version gone -- are no longer combined over the older history that was listed, which could bring a deleted letter back or type two letters twice. While you type, the note waits as above; otherwise both texts are kept, the note and one copy (#339).

  • The container builder base advances to Rust 1.98.1 with its pinned image digest; the repository compiler remains pinned to Rust 1.98.0 (#336).

  • A minimized desktop restores background throttling after unanswered network attempts, including while uploads remain queued for retry. An answer lifts it again while work remains. Logs distinguish calm, unanswered and stopped restoration (#283).

  • The left-selection regression sets up its settled large-file state directly, avoiding an unrelated initial download under CI load while retaining byte and request checks (#319).

  • CLI help and empty lists consistently describe saved servers. The unknown status command points to doctor, which checks local settings without contacting a server. Capability help states that same local-only boundary.

  • On Linux, an unset, empty or relative XDG_CONFIG_HOME uses $HOME/.config/obsync. Unsafe absolute destinations and invalid explicit --config-dir paths remain refused.

Install or update

In Obsidian: Settings -> Community plugins -> Browse -> Self Hosted Private Sync, or Check for updates if it is already installed. Obsidian takes the plugin files from this Release.

The server is a separate upgrade and nothing forces it: deploy the image below BY DIGEST, keeping both volumes, and the journal replays into the new binary.

Artifact Reference
Image ghcr.io/snaraj/obsync:v1.1.7@sha256:e0b314589f39eba7792bc8718f2f00422b475423045d16c788770858962ea377
Chart ghcr.io/snaraj/charts/obsync:1.1.7@sha256:b1f4216d5780f8448953bffe4aed60af05a85348dbc93f642858c33428cba08d
Plugin obsync-plugin-1.1.7.zip (sha256:d165f0ff8e5a5aaf72ca2b4ae681421064ffa5262482f2df99aeef23bf0539ff)
Server (linux/amd64) obsync-server-1.1.7-linux-amd64.tar.gz (sha256:4cb7086ed040a15880ce941ce3fad7238813d047805005036a5dc0308563d3c5)
Server (linux/arm64) obsync-server-1.1.7-linux-arm64.tar.gz (sha256:82eebfa186e2756c389e4cacc7f828cd4645625eddba8331263fcf1d48d49181)
Native CLI (linux-amd64) obsync-cli-1.1.7-linux-amd64.zip (sha256:d598804595982886541a08aa11865a7e93ae4b43a85f96e112a6c4d5ef27aabc)
Native CLI (linux-arm64) obsync-cli-1.1.7-linux-arm64.zip (sha256:daf675374d3a018b1918cb650d78c7210e1b8a57f04ba7b5d599dee6e81c3ffa)
Native CLI (darwin-arm64) obsync-cli-1.1.7-darwin-arm64.zip (sha256:6ee4a7c99f881e4ddf5487bc276b965fdab7b5e191ed11b04fa6921e7ff9ca24)
Native CLI (windows-amd64) obsync-cli-1.1.7-windows-amd64.zip (sha256:33125bac48259191f7ecb9f04f8525d333ba2ef85302ecb0fd7c264312770cf9)

Image and chart are signed with keyless Cosign by this workflow identity. The plugin files, server archives and native CLI archives carry this workflow's build provenance (gh attestation verify).

Publication evidence: obsync-1.1.7-release-manifest.json (sha256:56a1d37cc1e492dcc6d5983e3364101856846fdc61354c41acf42dd7e0bb64a3).

README file from

Github

Self Hosted Private Sync

Read in your language: English • العربية • Deutsch • Español • فارسی • Français • Bahasa Indonesia • Italiano • Nederlands • Polski • Português • Português (Brasil) • Русский • ไทย • Türkçe • Українська • Tiếng Việt • 日本語 • 한국어 • 中文简体 • 中文繁體

Self-hosted, end-to-end encrypted live sync for Obsidian. Your notes sync through a server you run yourself. Notes, attachments and file names are encrypted on your device, and the server never receives the key. The plugin works on every platform Obsidian runs on, desktop and mobile. There is no subscription and no account anywhere else.

Something not working? → Troubleshooting

Find what you need

Every page is also on the documentation site.

Use obsync

I want to… Go to
Choose how my devices reach my server Choose your setup
Set up everything on my home network, with every phone screen Same network, step by step
Install the plugin Install the plugin
Set up my first device Quickstart
Pair a phone or another computer Pair your phone
Know what the status icon and the commands mean Daily use and Reading the status bar
Get an older version of a note back Restore a retained version
Know what a setting does Settings
Deal with a conflict copy Conflicts
Fix a problem Troubleshooting
Get back in after losing a device Recovery
Move my vault to another server Moving this vault to a different server

Run a server

I want to… Go to
Run my server with Docker or Compose Run the server
Put it behind my own proxy (Caddy, nginx, Traefik, HAProxy) Already have a TLS terminator
Run it without a container, under systemd The static binary
Run my server on Kubernetes Kubernetes and the chart reference
Reach my server away from home, through my own VPN or proxy Reaching it from outside your LAN
Use Cloudflare (optional) Cloudflare
Trust my server's certificate on each device Trust the certificate authority
Know how much memory and disk it needs How much memory it needs and Storage
Back up my server Back up the two volumes
Upgrade my server Upgrade by digest
See my devices and revoke one The dashboard
Wipe my server and start again Purging a server
See what changed in each version CHANGELOG.md

Trust and privacy

I want to… Go to
Know what this plugin touches on my device and network What this plugin accesses
Understand what is encrypted and what the server can see Threat model and the dashboard's threat model
Report a security problem SECURITY.md

Inside the project

For contributors and reviewers: CONTRIBUTING.md, architecture, protocol, benchmarks, device validation runs and every page.

Install

The plugin's settings opening with Get started: the Setup guide row and its Open the guide button, above the Server URL field

Install the plugin from Settings → Community plugins → Browse. Search for Self Hosted Private Sync (plugin id obsync-private-sync). It needs Obsidian 1.13.0 or newer. Its settings open with the setup guide, one press away.

[!IMPORTANT]

  • It syncs to a server you run: no hosted service, no account elsewhere.
  • Back up your vault first; keep the 24-word recovery phrase off the device that made it.
  • Never run it alongside another sync (Obsidian Sync, a cloud folder, another plugin) on one vault.
  • Young software: read the CHANGELOG.md entry for your version, update every device, and know what each validation run covered.

Get syncing

The shortest complete path is Compose with Caddy on your own network, from a checkout of this repository. It gives you HTTPS on any network, with no domain and no account anywhere. Same network, step by step walks through it with every screen. Replace vX.Y.Z below with the release you are installing, the newest tag on the Releases page.

1. Verify the image. Then run exactly the digest it printed:

cosign verify ghcr.io/snaraj/obsync:vX.Y.Z \
  --certificate-identity https://github.com/snaraj/obsync/.github/workflows/release-publisher.yml@refs/heads/main \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com

2. Start the server:

OBSYNC_IMAGE=ghcr.io/snaraj/obsync@sha256:<digest> \
  OBSYNC_HOST=sync.example.org \
  OBSYNC_BIND_ADDRESS=192.168.1.10 \
  docker compose -f deploy/compose/docker-compose.yml up -d

OBSYNC_HOST is the name your devices will type. It only has to resolve on your own network. OBSYNC_BIND_ADDRESS is the address ports 80 and 443 are published on: a bind address limits the destination interface, not the source, so your firewall decides who reaches it. Compose refuses to start until you have chosen.

3. Read the setup token. At first boot the server mints a setup token and writes it to its journal volume, mode 0600, never logged. It creates your account once and remains the dashboard's recovery sign-in. Guard it like the recovery phrase. Ask the running server for it, in any shell, PowerShell and Command Prompt included:

docker exec obsync-obsync-1 obsyncd setup-token

If the container is stopped, read the file off it instead, from a POSIX shell (macOS, Linux, or WSL on Windows):

docker cp obsync-obsync-1:/data/journal/v1/setup-token - | tar -xO

4. Set up each device. Trust the server's certificate once (how). Install the plugin, then follow the Quickstart: set up the first device, then pair the others.

Already have HTTPS in front, from a proxy or tunnel you trust? Run the bare server instead.

What this plugin accesses

  • Your server, nothing else. Every request goes to the Server URL you type; no telemetry, no third party.
  • An account on that server, created from the setup token; your Obsidian account plays no part.
  • GitHub Releases, through Obsidian, for install and update; Obsidian ignores the extra release assets.
  • Your vault's file list, to decide what to sync; hidden (.obsidian, .git) and symlinked folders skipped.
  • The clipboard, written only by Copy code and Copy link in Pair a new device, never read.
  • Your browser, when you ask for the setup guide. It opens the project's guide there; the plugin itself sends nothing.

What the server can and cannot see: SECURITY.md and the threat model.

Versions

The LATEST release is the newest tag on the Releases page. That is what Obsidian installs and updates to. main is the EDGE: merged but unreleased work, for people building from source. There is no beta channel and no pre-release tag. The changelog's Unreleased section is the edge's record.

Questions, bugs, and security

  • A question, or not sure it is a bug: Discussions.
  • A bug: open an issue with the report Troubleshooting describes. Leave out any token, phrase or address you would not publish.
  • A suspected vulnerability: privately, through SECURITY.md, never a public issue.

License

MIT. See LICENSE.