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-scriptsguard is enabled, installation might warn about an unapprovedbetter-sqlite3install script. This package stores local sync state. A blocked native build might causeob loginorsync-setupto 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-remoteintentionally 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
obsidianaccount - Install
obsidian-headless - Resolve an allow-scripts warning for
better-sqlite3 - Check the
ob --helpcommand list for the installed version - Log in interactively as the
obsidianaccount - List remote vaults and confirm the vault name and capitalization
- Create and secure
~/obsidian-vaults - Run
sync-setupas 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.