Personal blog powered by a passion for technology.

Running Obsidian Sync Headlessly on Linux with systemd

Obsidian Sync no longer has to mean keeping the desktop app open somewhere.

In February 2026, Obsidian released obsidian-headless, an official CLI-only sync client. That changes a small but annoying part of my setup: I can now keep a vault synced on a server without a GUI, without xvfb, and without pretending Electron is a daemon.

This is useful if you want your vault available to scripts, AI agents, backup jobs, search indexers, or anything else running on a headless machine.

The important caveat: this still requires an active Obsidian Sync subscription. This is not a replacement for Obsidian Sync. It is a proper headless client for it.

The target setup

I wanted a boring Linux service:

  • runs on Ubuntu
  • syncs ~/Obsidian
  • starts automatically after boot
  • survives logout
  • stores credentials under my normal user account
  • avoids system-wide root-owned configuration

That points naturally to a user-level systemd service.

Install the CLI

The package requires Node.js 22 or newer. On this machine I use Node.js 24 through mise.

npm install -g obsidian-headless
ob --version

The binary is named ob.

One small operational detail matters here. If you use mise, the real binary lives under a versioned Node.js directory. That path can change when you upgrade Node.js. For systemd, use the stable shim instead:

~/.local/share/mise/shims/ob

That keeps the service unit independent from the currently installed Node.js version.

Connect the vault

First, log in interactively:

ob login

Then list the remote vaults available through Obsidian Sync:

ob sync-list-remote

Now go to the local vault directory and connect it to the remote vault:

cd ~/Obsidian
ob sync-setup --vault "Remote Vault"
ob sync-status

Run ob sync-status from inside the vault directory. If you run it elsewhere, it will not find the sync configuration, which is a mildly annoying failure mode the first time you hit it.

Pick the sync mode carefully

The default mode is the one most people want:

  • bidirectional: local and remote changes are merged
  • pull-only: remote changes are pulled locally, but local changes are not pushed
  • mirror: one side is made to match the other side

Be careful with mirror. It is useful for very specific workflows, but it is also the mode that can wipe data if you point it in the wrong direction.

You can change the mode later with:

ob sync-config

For my normal server setup, bidirectional is the right default.

Run it with systemd

Create this file:

# ~/.config/systemd/user/obsidian-sync.service
[Unit]
Description=Obsidian headless sync (continuous)
Documentation=https://github.com/obsidianmd/obsidian-headless
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
WorkingDirectory=%h/Obsidian
ExecStart=%h/.local/share/mise/shims/ob sync --continuous
Restart=on-failure
RestartSec=10
TimeoutStartSec=30

[Install]
WantedBy=default.target

This is a user service, not a system service. That is intentional.

The service runs as your normal user, reads your normal home directory, and uses the credentials created by ob login. No root-owned sync config. No sudo around the sync process.

Enable linger once so user services can keep running after logout and across reboots:

sudo loginctl enable-linger "$USER"

Then enable and start the service:

systemctl --user daemon-reload
systemctl --user enable --now obsidian-sync.service
systemctl --user status obsidian-sync.service

Operating it

The daily commands are exactly what you would expect:

systemctl --user status obsidian-sync
journalctl --user -u obsidian-sync -f
systemctl --user restart obsidian-sync
systemctl --user stop obsidian-sync

In continuous mode, healthy logs show a polling loop. On my setup it checks roughly every 30 seconds and reports Fully synced when there is nothing to do.

Gotchas

There are a few things worth remembering:

  • ob sync-status needs to run from inside the vault directory.
  • npm install -g can put ob under a versioned Node.js path, so systemd should call the mise shim.
  • Default synced file types include images, audio, PDFs, and videos.
  • Obsidian config under .obsidian/ is not synced by default.
  • Plugin and theme parity across machines needs explicit configuration through ob sync-config.

That last point is easy to miss. Syncing notes is not the same thing as syncing the full local Obsidian environment.

Why this is better than the old workaround

Before this, the realistic options were awkward.

You could keep a desktop Obsidian app running somewhere. You could run it with a virtual display. You could use a third-party sync path and accept that it was no longer Obsidian Sync. None of those options felt like infrastructure.

obsidian-headless does.

It turns Obsidian Sync into something that can live cleanly on a server:

  • a CLI
  • a working directory
  • a user service
  • logs
  • restarts
  • no graphical session

That is exactly the shape I want for a vault that is part of a larger personal automation system.

It is not dramatic software. It is better than that: it is boring software in the right place.