Jiří Čepelka 10 downloadsSync your vaults through your own self-hosted SimpleSync server. Enter the server address, name and password, then pick a vault.
Simple self-hosted sync for Obsidian: a small server plus an Obsidian plugin. No CouchDB, no S3 keys, no config files to tune.
In Obsidian you enter server address, name and password, pick a vault from the list, and you're done.
/p/<name>/.… (conflict …) is kept.Open App Store → "+" → Install a customized app, switch to YAML, delete what's there (Ctrl+A, Delete) and paste this (also in deploy/zimaos/docker-compose.yml):
name: obsisync
services:
obsisync:
image: ghcr.io/jirkacepelka/obsisync:latest
container_name: obsisync
restart: unless-stopped
ports:
- target: 8080
published: "8080"
protocol: tcp
environment:
TZ: Europe/Prague
volumes:
- type: bind
source: /DATA/AppData/obsisync/data
target: /data
x-casaos:
architectures:
- amd64
- arm64
main: obsisync
category: Utilities
title:
en_us: SimpleSync
tagline:
en_us: Simple self-hosted sync for Obsidian
icon: https://raw.githubusercontent.com/jirkacepelka/SimpleSync/main/server/internal/web/static/icon.svg
index: /
port_map: "8080"
scheme: http
Click Install, then open http://<zimaos-ip>:8080.
Create the administrator account (first-start wizard).
Data lives in /DATA/AppData/obsisync/data.
Access from outside your home network (a phone on mobile data): the easiest option is Tailscale (available in the ZimaOS App Store) or a Cloudflare Tunnel. HTTPS is recommended for iOS; Tailscale provides free HTTPS certificates (
tailscale serve).
A 1 GB RAM VPS with Docker is enough.
# 1) DNS: an A record sync.example.com → the VPS IP address
# 2) on the VPS:
mkdir obsisync && cd obsisync
curl -O https://raw.githubusercontent.com/jirkacepelka/SimpleSync/main/deploy/docker-compose.caddy.yml
DOMAIN=sync.example.com docker compose -f docker-compose.caddy.yml up -d
Open https://sync.example.com and create the administrator. Caddy obtains the Let's Encrypt certificate by itself.
docker run -d --name obsisync -p 8080:8080 -e TZ=Europe/Prague \
-v $PWD/data:/data --restart unless-stopped ghcr.io/jirkacepelka/obsisync:latest
Pull the new image and recreate the container (on ZimaOS: the app's settings → update / reinstall). Your data in /data is kept.
In the web admin click Download for Obsidian next to a vault (or Plugin → Download ready-made vault for an empty one). You get a ZIP with a folder that contains the vault's notes and the SimpleSync plugin, already installed and set up for your server and name (no password or token is ever put in the ZIP).
<vault>/.obsidian/plugins/ (a simplesync folder appears) and enable Settings → Community plugins → SimpleSync. On a phone, the easiest way is the BRAT plugin with the repository jirkacepelka/SimpleSync.The status bar icon shows the state: ✓ synced, ⟳ syncing, ⚡ server unreachable, ⚠ error. Click it to sync right away. The plugin language follows Obsidian; you can change it in the plugin settings.
⚠️ Connecting to a server vault that already has files replaces the content of this Obsidian vault. Nothing is uploaded from this device; local files that differ are moved to Obsidian's trash (
.trash). The plugin shows a warning in its settings and asks before connecting. If you want to upload an existing vault, use "Create a new vault from this one" instead.
.trash), so nothing is lost. An empty or brand-new Obsidian vault can never overwrite the server.After the first sync, both directions sync normally.
Open a vault in the web admin and click Open notes (or the Notes tab). You get a file tree, an editor and a live preview side by side, so you can write from any browser without installing Obsidian.
name (web).… (conflict …).md copy, exactly like on a device.[[wikilinks]] (with |alias and #heading), embedded images ![[photo.png]], callouts > [!tip], ==highlights==, #tags, task lists, tables and properties (front matter).[[ suggests notes to link. The toolbar and shortcuts (Ctrl+B/I/K) cover the usual formatting; lists continue on Enter.A vault owner can publish notes as a small public website: Vault → Settings → Publishing.
https://<your server>/p/<address>/.publish: true in their properties; the Publish button in the editor toggles it) or a whole folder (empty = the whole vault).Only published notes are reachable. Links to notes that aren't published are shown as plain text, so their names and paths don't leak, and an image or file is served only when a published note links to or embeds it. Visitors need no account. Raw HTML in notes is never rendered, and public pages send a strict Content-Security-Policy.
| Section | What it does |
|---|---|
| Overview | vaults, disk usage, devices online, alerts about failed backups |
| Vaults | create a vault (name, backup frequency, how long to keep backups, members) |
| → Notes | the web editor (see above) |
| → Files | browse folders, preview notes and images, version history with restore, download the whole vault as ZIP |
| → Trash | deleted files and restoring them |
| → Backups | list of backups, Back up now, ZIP download, restore a single file or the whole vault |
| → Members | share the vault with other users: Owner / Editor / Read only |
| → Settings | rename, change the backup plan, publishing, delete the vault |
| Users | create accounts, reset passwords, administrators |
| Devices | every Obsidian login; logging a device out removes its access immediately |
| Settings | how long to keep version history, maximum file size, whether users may create vaults |
The language picker is at the bottom of every page.
When creating a vault, the administrator chooses:
data/backups/, handy for copying to another diskHow it works:
Everything (database, file contents, ZIP backups) is in the data/ folder. Back up that folder. For a consistent copy of the database while running:
docker exec obsisync obsisync backup-db /data/obsisync-backup.db
docker exec obsisync obsisync reset-password admin NewPassword123
(If the user doesn't exist, it is created as an administrator.)
The SimpleSync plugin communicates only with the SimpleSync server whose address you enter, a server you run yourself. It sends your login once to obtain a device token (the password is not stored), then uploads and downloads the files of the connected vault. To do that it lists all files in the vault and compares them with the server; nothing is sent anywhere else. There is no telemetry, no third-party service and no account with anyone else. Content is protected in transit by HTTPS when the server is reachable over HTTPS; it is not end-to-end encrypted, so whoever runs the server can read the notes stored on it.
X-Forwarded-For. Devices and browsers that are already logged in keep working while an account is throttled.HttpOnly and SameSite=Lax, every form has a CSRF token, pages send a strict Content-Security-Policy and may not be framed, and files from vaults are only ever downloaded (never rendered as HTML/SVG)..obsidian are only synced when you turn that on; only do so in vaults shared with people you trust, because synced plugins run on every device.http:// outside your home network.X-Forwarded-For only when the connection comes from a local or private address, and then only the entry added by the nearest proxy.Obsidian (desktop / mobile) Server (1 Docker container)
┌───────────────────────┐ HTTPS (REST) ┌────────────────────────────────┐
│ SimpleSync plugin │◄────────────────►│ SimpleSync server (Go) │
│ • 3 fields + picker │ WebSocket │ • /api/v1 sync │
│ • sync engine │◄──────────────────│ • / web admin │
│ • 3-way merge │ ("new revision") │ • SQLite metadata, history │
└───────────────────────┘ │ • blobs/ content (SHA-256) │
│ • backup scheduler + upkeep │
└────────────────────────────────┘
How sync works
Note (conflict 2026-09-25 1530 jirka).md (with the name of the user whose edit it is),.trash/, .git/, other hidden folders, workspace.json and the plugin's own data (token). The .obsidian folder can be enabled with a toggle.API: everything is under /api/v1, authorized with Authorization: Bearer <device token>:
| Endpoint | Purpose |
|---|---|
POST /auth/login |
name + password → device token (the password is not stored on the device) |
GET /vaults, POST /vaults |
the user's vaults / create one |
GET /vaults/{id}/changes?since=REV |
changes since a revision |
POST /vaults/{id}/blobs/missing |
which content the server doesn't have yet |
PUT / GET /vaults/{id}/blobs/{sha256} |
upload / download content |
POST /vaults/{id}/commit |
a batch of changes (compare-and-swap) |
GET /vaults/{id}/ws |
WebSocket notifications |
server/ Go server (cmd/obsisync, internal/{store,api,web,backup,blobs,auth,hub,i18n,markdown,textmerge})
plugin/ Obsidian plugin (TypeScript); src/engine is Obsidian-independent and tested
deploy/ docker-compose for home network, VPS with Caddy, and ZimaOS
Dockerfile multi-arch image (amd64 + arm64) with the server and the plugin
# server
cd server && go test ./... && go run ./cmd/obsisync # http://localhost:8080, data in ./data
# plugin (tests start a real server and simulate several devices)
cd plugin && npm ci && npm test && npm run build # output in plugin/dist
Translations: the web admin uses server/internal/i18n/locales/<lang>.json, the plugin uses plugin/src/i18n/<lang>.ts. English is the source; missing keys fall back to English.
Releases: every push to main makes GitHub Actions build the Docker image ghcr.io/jirkacepelka/obsisync (latest and the version from manifest.json) and a GitHub release with the plugin files. To publish a new version, bump version in manifest.json, plugin/manifest.json and versions.json.
| Variable | Default | Meaning |
|---|---|---|
TZ |
UTC | time zone for displayed times (e.g. Europe/Prague) |
OBSISYNC_DATA |
/data |
data folder |
OBSISYNC_ADDR |
:8080 |
listen address and port |
OBSISYNC_BACKUP_DIR |
$OBSISYNC_DATA/backups |
where ZIP backups go |