Self-host Synch without Cloudflare
Run the Synch server on your own hardware with Docker Compose or plain systemd - no Cloudflare account, no third-party services required.
If you’d rather not use Cloudflare at all, Synch’s server also runs as a plain Node.js process. This guide covers running it on your own hardware - a homelab NAS, a small VPS, an Incus/LXC container, anywhere you can run a long-lived process and expose a port - with either Docker Compose or systemd.
Data is stored entirely on disk: one SQLite file per vault, plus a small app database for accounts and vault metadata. There’s no D1, no Durable Objects, no R2 - just files in a directory you control.
If you’d rather deploy to a free Cloudflare account instead, see the Cloudflare self-hosting guide.
Before you deploy
Either way, you’ll need a way to reach the server from your Obsidian devices - either on your local network, or over the internet if you want to sync away from home (put it behind a reverse proxy with TLS in that case; the server itself only speaks plain HTTP).
Deploy with Docker Compose
You’ll need Docker and Docker Compose installed.
-
Clone the repository and go to the API directory:
git clone https://github.com/hjinco/synch.git cd synch/apps/api -
Copy the example environment file:
cp .env.example .env -
Edit
.envand fill in:-
PUBLIC_URL- the address clients will reach this server at (e.g.http://192.168.1.50:8787on your LAN, orhttps://synch.your-domain.examplebehind a reverse proxy). This must match exactly, including scheme and port - it’s used to validate incoming request origins. -
BETTER_AUTH_SECRETandSYNC_TOKEN_SECRET- two independent random secrets. Generate each with:openssl rand -hex 32 -
AUTH_ALLOWED_EMAILS(required) - a comma-separated list of the exact email addresses allowed to create accounts:AUTH_ALLOWED_EMAILS=you@example.com,family@example.comMatching ignores letter case and surrounding spaces. This setting only controls new accounts, so existing users can still sign in after their address is removed from the list.
-
-
Start the server:
docker compose up -d
Vault and blob data persist in the synch-data Docker volume across restarts and upgrades. To move to a new host, copy that volume along with your .env file.
Blob storage
By default, encrypted file contents are stored on disk inside the same data volume. If you’d rather use an S3-compatible bucket (AWS S3, MinIO, R2, etc.), uncomment and fill in the BLOB_STORAGE=s3 block in .env before starting the container.
Upgrading
git pull
docker compose up -d --build
Database migrations run automatically on startup.
Run without Docker (systemd)
The server is just a Node.js process (src/self-host.ts, run with tsx) - Docker is a convenience, not a requirement. This is a reasonable choice if you’re already running a VM (an Incus/LXC container, for example) and would rather not add Docker as a dependency.
On Debian/Ubuntu, install.sh automates everything below - clone the repo, then cd apps/api && sudo ./install.sh. It’s safe to re-run (e.g. after git pull) to update and restart. The manual steps:
-
Install Node.js 24 and a build toolchain (needed for
better-sqlite3’s native module if no prebuilt binary matches your VM’s architecture/libc):# Debian/Ubuntu sudo apt install -y nodejs python3 make g++ sudo corepack enable -
Clone the repository and install production dependencies:
git clone https://github.com/hjinco/synch.git /opt/synch cd /opt/synch/apps/api pnpm install --frozen-lockfile --filter @synch/api... --prod -
Copy
.env.exampleto.envand fill it in exactly as in the Docker Compose steps above. -
Install and start the example systemd unit:
sudo useradd --system --home /var/lib/synch-api --create-home synch sudo cp synch-api.service.example /etc/systemd/system/synch-api.service # edit the unit's paths/User/Group if you didn't use /opt/synch or the synch user sudo systemctl daemon-reload sudo systemctl enable --now synch-api -
Check it’s up:
curl http://localhost:8787/health sudo journalctl -u synch-api -f
Upgrading (systemd)
cd /opt/synch
git pull
cd apps/api
pnpm install --frozen-lockfile --filter @synch/api... --prod
sudo systemctl restart synch-api
Binding to localhost only
By default the server listens on 0.0.0.0 (all interfaces) - Docker Compose needs this to make its port publishing work. If you’re running the systemd deployment behind a local reverse proxy (e.g. tailscale serve, nginx, Caddy) and don’t want the server directly reachable on your LAN or public interface, set HOST=127.0.0.1 in .env. The example systemd unit (and install.sh) already does this for you.
After changing it, update PUBLIC_URL to the address clients will actually connect through (your reverse proxy’s address, not 127.0.0.1) and restart the service - PUBLIC_URL is what better-auth validates incoming request origins against.
Connect Obsidian
- Open Obsidian.
- Go to Settings.
- Open Synch.
- In Self-hosted server, paste your server’s
PUBLIC_URL. Do not include a trailing/. - Click Save.
- Continue sign-in and vault setup normally.