Synch

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.

  1. Clone the repository and go to the API directory:

    git clone https://github.com/hjinco/synch.git
    cd synch/apps/api
  2. Copy the example environment file:

    cp .env.example .env
  3. Edit .env and fill in:

    • PUBLIC_URL - the address clients will reach this server at (e.g. http://192.168.1.50:8787 on your LAN, or https://synch.your-domain.example behind a reverse proxy). This must match exactly, including scheme and port - it’s used to validate incoming request origins.

    • BETTER_AUTH_SECRET and SYNC_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.com

      Matching 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.

  4. 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:

  1. 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
  2. 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
  3. Copy .env.example to .env and fill it in exactly as in the Docker Compose steps above.

  4. 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
  5. 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

  1. Open Obsidian.
  2. Go to Settings.
  3. Open Synch.
  4. In Self-hosted server, paste your server’s PUBLIC_URL. Do not include a trailing /.
  5. Click Save.
  6. Continue sign-in and vault setup normally.