Version 1.1.0. Last updated July 28, 2026.

Use the official Obsidian Headless command-line client to keep an Obsidian Sync vault available on a Linux server without installing the desktop application.

This guide configures continuous, bidirectional synchronization with a dedicated Linux account and a systemd service.

Important: Obsidian Headless connects to Obsidian Sync. You need an Obsidian account with access to the remote vault. Keep a separate backup. Synchronization does not replace backups.

Requirements

  • A Linux server with systemd
  • Node.js 22 or newer
  • npm
  • An Obsidian account with access to the remote Sync vault
  • The vault encryption password if it uses end-to-end encryption
  • sudo or root access for the initial installation

Check the installed versions:

node --version
npm --version

The Node.js result must be v22 or newer. If Node.js is missing or too old, install a supported release using your distribution instructions or the official Node.js download page.

1. Create a dedicated service account

Create a dedicated account with a home directory. The account remains locked for password login, but its Bash shell lets administrators enter it with sudo -iu during setup:

sudo useradd --create-home --shell /bin/bash obsidian

Create the parent directory for the vault:

sudo mkdir -p /home/obsidian/obsidian-vaults
sudo chown -R obsidian:obsidian /home/obsidian/obsidian-vaults
sudo chmod 750 /home/obsidian/obsidian-vaults

The path resolves to ~/obsidian-vaults under the obsidian account.

2. Install Obsidian Headless

Install the official npm package globally:

sudo npm install --global obsidian-headless

Confirm the command is available:

ob --help
command -v ob

Record the path returned by command -v ob. The systemd service needs this exact path. Common results include /usr/bin/ob and /usr/local/bin/ob.

Update the package later with:

sudo npm update --global obsidian-headless

Warning: If npm’s allow-scripts guard is enabled, installation might warn about an unapproved better-sqlite3 install script. This package stores local sync state. A blocked native build might cause ob login or sync-setup to fail.

Review the package, then repeat the global install with explicit permission for its install script:

sudo npm install --global --allow-scripts=better-sqlite3 obsidian-headless

npm approve-scripts only works inside a project with a package.json. It does not approve scripts for global installs.

Confirm the real command list before relying on this guide. The commands below were verified with Obsidian Headless 0.0.13. If ob --help omits a command in your installed version, follow the installed help output.

3. Log in as the service account

Run login as the dedicated user. This stores credentials under the same account that runs the service.

sudo -iu obsidian
ob login

Enter the Obsidian email address, password, and MFA code when prompted. Do not pass the password as a command-line argument. Shell history and process listings might expose it.

Run ob login again to check the login later:

ob login

4. Find the remote vault

List remote vaults available to the account:

ob sync-list-remote

Request machine-readable output when needed:

ob sync-list-remote --json

Record the exact vault name or ID, including capitalization. Use the literal value returned by the command.

5. Connect the local directory

Still working as the obsidian user, connect the empty local directory to the existing remote vault:

ob sync-setup \
  --vault "MyVault" \
  --path ~/obsidian-vaults \
  --device-name "linux-server"

If the vault uses end-to-end encryption, the command prompts for its encryption password. This differs from the Obsidian account password.

Verify the configuration:

ob sync-status --path ~/obsidian-vaults
ob sync-config --path ~/obsidian-vaults

If the local directory contains notes, sync-setup warns about merging them with the remote vault. The most recent version wins conflicts. Review the vault in Obsidian after the first sync.

6. Choose the sync mode

The observed default uses bidirectional sync with a merge conflict strategy. Set both values explicitly:

ob sync-config \
  --path ~/obsidian-vaults \
  --mode bidirectional \
  --conflict-strategy merge \
  --device-name "linux-server"
Mode Behavior Best use
bidirectional Uploads and downloads changes A server that reads and modifies vault files
pull-only Downloads remote changes and ignores local changes Read-only processing or backup-like workflows
mirror-remote Downloads remote state and reverts local changes A disposable local mirror

Warning: mirror-remote intentionally reverts local changes. Do not use it for a directory containing unique files.

Configure attachment syncing when needed:

ob sync-config \
  --path ~/obsidian-vaults \
  --file-types image,audio,video,pdf,unsupported

Configure Obsidian settings syncing when needed:

ob sync-config \
  --path ~/obsidian-vaults \
  --configs app,appearance,hotkey,core-plugin,core-plugin-data

A server that only needs Markdown content has less clutter and risk when configuration files and unnecessary attachment types stay disabled.

7. Perform the first sync manually

Run a one-time sync before creating the service:

ob sync --path ~/obsidian-vaults

Inspect the downloaded vault:

find ~/obsidian-vaults -maxdepth 2 -type f | head -50

Check sync status again:

ob sync-status --path ~/obsidian-vaults

Continue after the manual sync succeeds. If the directory had existing notes, confirm the merge looks correct first.

Exit the service-account shell:

exit

8. Create the systemd service

Confirm the executable path outside the service-account shell:

command -v ob

Create the unit file:

sudo nano /etc/systemd/system/obsidian-headless.service

Paste this unit:

[Unit]
Description=Obsidian Headless continuous vault sync
Wants=network-online.target
After=network-online.target

[Service]
Type=simple
User=obsidian
Group=obsidian
WorkingDirectory=/home/obsidian/obsidian-vaults
ExecStart=/usr/bin/ob sync --path /home/obsidian/obsidian-vaults --continuous
Restart=on-failure
RestartSec=10

NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=read-only
ReadWritePaths=/home/obsidian/obsidian-vaults /home/obsidian

[Install]
WantedBy=multi-user.target

Replace /usr/bin/ob if command -v ob returned another path. The --continuous flag watches for local and remote changes, then syncs them without a manual trigger.

Reload systemd and enable the service:

sudo systemctl daemon-reload
sudo systemctl enable --now obsidian-headless.service

9. Verify continuous sync

Check service status:

sudo systemctl status obsidian-headless.service

Follow its logs:

sudo journalctl -u obsidian-headless.service -f

View logs from the current boot:

sudo journalctl -u obsidian-headless.service -b --no-pager

Create a harmless test note from another synced device and confirm it appears on the server. Journal lines should include Push:, Downloading, Downloaded, and Accepted.

Create a temporary note on the server and confirm it reaches the other device. Look for New file, Uploading file, and Upload complete. Delete both test notes after the test. Their deletions should sync too.

Routine administration

Restart the sync client

sudo systemctl restart obsidian-headless.service

Stop continuous sync

sudo systemctl stop obsidian-headless.service

Review the vault configuration

sudo -iu obsidian ob sync-config --path ~/obsidian-vaults
sudo -iu obsidian ob sync-status --path ~/obsidian-vaults

List configured local vaults

sudo -iu obsidian ob sync-list-local

Disconnect the server from the remote vault

Stop the service, then unlink it:

sudo systemctl stop obsidian-headless.service
sudo -iu obsidian ob sync-unlink --path ~/obsidian-vaults

Unlinking disconnects Sync. It does not delete the local vault directory.

Back up the vault

Back up ~/obsidian-vaults to storage independent of Obsidian Sync. Retain several historical versions so you can recover from deletion, corruption, or unwanted edits.

Decide whether your backup should include temporary Sync files, plugin data, and .obsidian settings. Back up the entire vault directory for the most complete recovery.

Troubleshooting

ob: command not found

Find npm’s global prefix and the installed executable:

npm prefix --global
command -v ob

Use the full executable path in the systemd unit.

Install warns about better-sqlite3 and allow-scripts

Repeat the global installation with explicit permission for better-sqlite3 before trusting login or setup:

sudo npm install --global --allow-scripts=better-sqlite3 obsidian-headless

Service works manually but fails under systemd

Confirm login and sync-setup ran under the obsidian account:

sudo -iu obsidian ob login
sudo -iu obsidian ob sync-status --path ~/obsidian-vaults

Verify ownership:

sudo chown -R obsidian:obsidian /home/obsidian/obsidian-vaults

End-to-end encryption error

Run sync-setup interactively again as the service account and enter the correct vault encryption password. Keep the password out of command-line arguments.

Permission denied

Check each parent directory and the vault:

namei -l /home/obsidian/obsidian-vaults

The obsidian account needs permission to traverse its home directory and read-write permission inside the vault.

High CPU or repeated restarts

sudo systemctl status obsidian-headless.service
sudo journalctl -u obsidian-headless.service -n 200 --no-pager

Stop the service during investigation:

sudo systemctl stop obsidian-headless.service

Deployment checklist

  • Install Node.js 22 or newer and npm
  • Create the dedicated obsidian account
  • Install obsidian-headless
  • Resolve an allow-scripts warning for better-sqlite3
  • Check the ob --help command list for the installed version
  • Log in interactively as the obsidian account
  • List remote vaults and confirm the vault name and capitalization
  • Create and secure ~/obsidian-vaults
  • Run sync-setup as the service account
  • Select the sync mode and conflict strategy
  • Complete and verify a one-time sync
  • Review merged notes if the directory held existing files
  • Confirm the path returned by command -v ob
  • Install and enable the systemd service
  • Test synchronization in both directions
  • Configure an independent, versioned backup

Changelog

  • 1.0.0: Original draft
  • 1.1.0: Corrected the default conflict strategy to merge, added allow-scripts troubleshooting, and verified the full command set against a live installation

Official references

Questions or corrections: claire@users.noreply.github.com. Source projects: Makeea on GitHub.