Running Obsidian headlessly on Ubuntu for AI agent workflows (Xvfb + systemd setup)

I’m running Obsidian 1.12.7 on a headless Ubuntu 24.04 server for AI agent workflows via the CLI. It works — here’s what I had to solve and the full setup.

What I had to solve

1. GPU crash-loop under Xvfb. The Chromium GPU process crashes on every startup (Exiting GPU process due to errors during initialization), causing a restart loop. --disable-gpu --disable-software-rasterizer fixes this. Obsidian could auto-detect the absence of a GPU and disable it — or honor --ozone-platform=headless as @jiywww suggested (I haven’t tested this yet but it sounds like it might avoid needing Xvfb entirely).

2. Stale singleton locks after SIGKILL. After an ungraceful shutdown (OOM, systemctl stop), SingletonLock, SingletonSocket, and SingletonCookie in ~/.config/obsidian/ block the next launch. Obsidian sees the dead PID and exits silently with status 0. The service file cleans these up before each start. Obsidian could self-clean when the lock PID is dead.

3. obsidian restart exits with code 0. With Restart=on-failure in systemd, the service doesn’t restart because exit 0 isn’t a failure. A distinct exit code (e.g., 75) for “restart requested” would let service managers handle this correctly. Workaround: Restart=always instead of Restart=on-failure.

What would make this easier

All three are workaroundable, but each requires independent debugging to discover. A --headless flag (as the OP suggests) that bundled these fixes would make server deployment much smoother.

Full setup

systemd user service

This single service handles Xvfb, GPU flags, and singleton lock cleanup:

# ~/.config/systemd/user/obsidian.service
[Unit]
Description=Obsidian (headless via Xvfb)
After=default.target

[Service]
Type=simple

# Clean up stale Electron singleton files from previous crashes
ExecStartPre=/bin/sh -c 'rm -f %h/.config/obsidian/SingletonLock %h/.config/obsidian/SingletonSocket %h/.config/obsidian/SingletonCookie'

ExecStart=/usr/bin/xvfb-run \
  --auth-file=%h/.config/obsidian/Xauthority \
  --server-num=99 \
  /opt/Obsidian/obsidian \
  --disable-gpu \
  --disable-software-rasterizer

Environment=DISPLAY=:99
Restart=always
RestartSec=10

[Install]
WantedBy=default.target

systemctl --user enable --now obsidian.service

CLI wrapper

The bundled obsidian-cli needs DISPLAY set to find the running instance’s IPC socket. A wrapper in ~/.local/bin/ handles this:

#!/bin/sh
# ~/.local/bin/obsidian
# Wrapper for Obsidian CLI that sets DISPLAY for headless Xvfb sessions.
export DISPLAY="${DISPLAY:-:99}"
exec /opt/Obsidian/obsidian-cli "$@"

Then the CLI works normally:

obsidian open "My Note"

Happy to answer questions if anyone else is running a similar setup.

1 Like