Self-hosted file synchronization for Obsidian Vault notes.
obsync keeps your Obsidian vault in sync across multiple devices using your own server. A Go server stores files and metadata, while a Rust client watches your vault and syncs changes in real time over WebSocket.
- Real-time sync via WebSocket with automatic reconnection
- Optional end-to-end encryption (AES-256-GCM) -- the server stores only ciphertext
- Conflict detection with local backup files
- File rename support coordinated between devices
- JWT authentication with device management
- SHA-256 hash verification for file integrity
- Local SQLite index for offline-capable metadata
- Configurable ignore patterns and file size limits
- Self-hosted -- your data stays on your server
- System service integration (launchd on macOS, systemd on Linux, Task Scheduler on Windows)
- Docker deployment support
# 1. Install the client
curl -sSL https://github.com/cluion/obsync/releases/latest/download/obsync-install.sh | sh
# 2. Set up your server (or use an existing one)
docker compose -f deploy/docker-compose.yml up -d
# 3. Connect and sync
obsync init # enter server URL, username, and vault path
obsync login # authenticate with the server
obsync start # start the sync daemonBinary (macOS / Linux / Windows):
Download the latest release from GitHub Releases.
On Windows, obsync install registers a per-user Scheduled Task that starts the
sync daemon at logon (no administrator rights required); obsync uninstall
removes it.
Homebrew:
brew tap cluion/tap
brew install obsyncInstall script:
curl -sSL https://github.com/cluion/obsync/releases/latest/download/obsync-install.sh | shFrom source:
git clone https://github.com/cluion/obsync.git
cd obsync
make build-client
# binary at apps/client/target/release/obsyncBinary:
Download from GitHub Releases.
Docker:
docker compose -f deploy/docker-compose.yml up -dFrom source:
make build-server
# binary at bin/obsync-serverThe server reads config.toml from its working directory. A full example lives
at deploy/server.toml:
[server]
listen = "0.0.0.0:8080"
[storage]
backend = "disk" # "disk" or "s3"
data_dir = "./data"
db_path = "obsync.db"
min_free_mb = 1024
[auth]
jwt_secret = "" # leave empty to auto-generate and persist a secret
[rate_limit]
enabled = true # throttle auth endpoints against brute-force
auth_requests_per_minute = 10
auth_burst = 5
trusted_proxies = [] # see note below if running behind a reverse proxyBehind a reverse proxy: set
trusted_proxiesto your proxy's address (e.g.["127.0.0.1/32", "::1/128"]for a local nginx). The server only reads the real client IP fromX-Real-IP/X-Forwarded-Forwhen the request arrives from a trusted proxy; otherwise those headers are ignored so clients cannot spoof them to bypass rate limiting.
S3-compatible storage can also be configured via environment variables
(OBSYNC_STORAGE_BACKEND=s3, OBSYNC_S3_ENDPOINT, OBSYNC_S3_BUCKET,
OBSYNC_S3_ACCESS_KEY, OBSYNC_S3_SECRET_KEY, OBSYNC_S3_REGION).
On first launch the server prompts you to create an admin account. obsync is designed for a single user syncing across multiple devices, self-hosted on your own server.
Terminate TLS with a reverse proxy. A ready-to-adapt nginx config — including
WebSocket upgrade handling and rate limiting for auth endpoints — is provided at
deploy/nginx.conf. For containerized deploys see
deploy/docker-compose.yml.
obsync can encrypt file contents with AES-256-GCM before they leave your device, so the server only ever stores ciphertext.
obsync e2ee setup # fresh vault: generate a master key + print recovery phrase
obsync e2ee migrate # existing plaintext vault: re-encrypt all files on the server
obsync e2ee status # show E2EE state for the active vault
obsync e2ee export # re-display the recovery phrase
obsync e2ee import # on another device: paste the phrase to share the keyUse setup on a brand-new vault, or migrate to turn on E2EE for a vault that
has already been synced in plaintext (it re-encrypts and re-uploads everything;
stop obsync on your other devices first, then import the recovery phrase
there).
The master key is stored in your OS keyring and never written to disk in plaintext. Write down the recovery phrase — it is the only way to decrypt your vault if you lose all devices. File paths, sizes, and timestamps remain visible to the server (needed for sync); only contents are encrypted.
obsync doctor # run diagnostic checks (config, connectivity, auth, paths)
obsync status # show daemon and sync status
obsync log --follow # follow daemon logsIf sync stalls, obsync doctor is the fastest first step; it validates the
server URL, credentials, vault path, and local index. See
CONTRIBUTING.md to report issues, and
SECURITY.md for the security policy.
obsync init # Interactive setup (server URL, vault path)
obsync login # Authenticate with the server
obsync start # Start sync daemon in background
obsync stop # Stop the sync daemon
obsync sync # One-time manual sync
obsync status # Show daemon and sync status
obsync doctor # Run diagnostic checks
obsync log --follow # Follow daemon logs
obsync rename old.md new.md # Rename a file across devicesobsync uses a client-server model:
- Server (Go): Stores files on disk and metadata in SQLite. Broadcasts change events to connected clients over WebSocket. Provides a REST API for file operations, authentication, and device management.
- Client (Rust): Watches the local vault for filesystem changes. Syncs with the server via REST API and receives real-time updates over WebSocket. Maintains a local SQLite index for tracking file state and sync progress.
make build # Build both server and client
make test # Run all tests
make lint # Run all lintersThis project is licensed under the terms found in the LICENSE file.