# Overview
This documents setting up a headless [[Obsidian]] instance on a Linux server for vault syncing via [[Obsidian Sync]], mostly based on [this guide](https://rolle.design/setting-up-a-headless-obsidian-instance-for-syncing).
The setup uses:
- **Xvfb**: Virtual framebuffer (fake display)
- **[[Openbox]]**: Minimal window manager
- **x11vnc**: VNC server for remote access
- **[[Obsidian]]**: Running with GPU disabled
# Installation
## Dependencies
```bash
sudo apt-get install -y openbox xvfb python3-xdg x11vnc xdg-utils \
libnotify4 libnss3 libsecret-1-0 libasound2
```
## Obsidian
```bash
# Download latest version (check https://obsidian.md/download for current release)
wget https://github.com/obsidianmd/obsidian-releases/releases/download/v1.11.5/obsidian_1.11.5_amd64.deb
sudo dpkg -i obsidian_1.11.5_amd64.deb
```
# Configuration
## VNC Password
```bash
# Set VNC password (interactive)
x11vnc -storepasswd
chmod 600 ~/.vnc/passwd
# Restart x11vnc after changing password
sudo systemctl restart obsidian-headless-x11vnc.service
```
# Systemd Services
Four services work together to run Obsidian headlessly.
## Xvfb (Virtual Framebuffer)
**File**: `/etc/systemd/system/obsidian-headless-xvfb.service`
```ini
[Unit]
Description=Headless Xvfb for Obsidian GUI
[Service]
User=<YOUR_USER>
Group=<YOUR_USER>
WorkingDirectory=/home/<YOUR_USER>/
ExecStart=/usr/bin/Xvfb :5 -extension GLX -screen 0 800x600x16
KillSignal=SIGINT
Environment="PATH=/usr/local/bin:/usr/bin:/bin"
Type=exec
Restart=on-failure
RestartSec=30s
[Install]
WantedBy=multi-user.target
```
## Openbox (Window Manager)
**File**: `/etc/systemd/system/obsidian-headless-openboxsession.service`
```ini
[Unit]
Description=Headless openbox-session for Obsidian GUI
After=obsidian-headless-xvfb.service
[Service]
User=<YOUR_USER>
Group=<YOUR_USER>
WorkingDirectory=/home/<YOUR_USER>/
ExecStart=/usr/bin/openbox-session
KillSignal=SIGINT
Environment="DISPLAY=:5"
Environment="PATH=/usr/local/bin:/usr/bin:/bin"
Type=exec
Restart=on-failure
RestartSec=30s
[Install]
WantedBy=multi-user.target
```
## x11vnc (VNC Server)
**File**: `/etc/systemd/system/obsidian-headless-x11vnc.service`
```ini
[Unit]
Description=Headless x11vnc for Obsidian GUI
After=obsidian-headless-openboxsession.service
[Service]
User=<YOUR_USER>
Group=<YOUR_USER>
WorkingDirectory=/home/<YOUR_USER>/
ExecStart=/usr/bin/x11vnc -rfbport 5900 -display :5 -rfbauth /home/<YOUR_USER>/.vnc/passwd -forever -shared -noxdamage
KillSignal=SIGINT
Environment="PATH=/usr/local/bin:/usr/bin:/bin"
Type=exec
Restart=on-failure
RestartSec=30s
[Install]
WantedBy=multi-user.target
```
Note: The `-forever -shared -noxdamage` flags are important:
- `-forever`: Keep listening after client disconnects
- `-shared`: Allow multiple simultaneous connections
- `-noxdamage`: Disable X DAMAGE extension (fixes issues with some compositors)
## Obsidian
**File**: `/etc/systemd/system/obsidian-headless.service`
```ini
[Unit]
Description=Headless Obsidian
After=obsidian-headless-xvfb.service
[Service]
User=<YOUR_USER>
Group=<YOUR_USER>
WorkingDirectory=/home/<YOUR_USER>/
ExecStart=/usr/bin/obsidian --no-sandbox --disable-gpu --disable-software-rasterizer --disable-gpu-compositing
KillSignal=SIGINT
Environment="DISPLAY=:5"
Environment="PATH=/usr/local/bin:/usr/bin:/bin"
Type=exec
Restart=on-failure
RestartSec=30s
[Install]
WantedBy=multi-user.target
```
Note: GPU flags are required because Xvfb doesn't have GPU/GLX support:
- `--disable-gpu`: Disable GPU hardware acceleration
- `--disable-software-rasterizer`: Disable software GL fallback
- `--disable-gpu-compositing`: Disable GPU compositing
## Global Display Configuration
The [[Obsidian]] CLI (v1.12+) uses [[Electron]]'s single-instance IPC to communicate with the running headless instance. This requires the `DISPLAY` variable to be set, even for non-interactive SSH commands like `gcloud compute ssh ... -- obsidian help`.
Shell profile files (`.profile`, `.bashrc`) are **not sourced** for non-interactive SSH sessions, so setting `DISPLAY` there is not sufficient. Instead, set it in `/etc/environment`, which is read by PAM for all session types:
**File**: `/etc/environment`
```bash
DISPLAY=:5
```
This ensures `obsidian` CLI commands work transparently from any SSH context without needing to manually export `DISPLAY`.
## Service Management
```bash
# Enable all services (auto-start on boot)
sudo systemctl enable obsidian-headless-xvfb obsidian-headless-openboxsession
sudo systemctl enable obsidian-headless-x11vnc obsidian-headless
# Start all services
sudo systemctl start obsidian-headless-xvfb
sudo systemctl start obsidian-headless-openboxsession
sudo systemctl start obsidian-headless-x11vnc
sudo systemctl start obsidian-headless
# Check status
sudo systemctl status obsidian-headless-xvfb obsidian-headless-openboxsession
sudo systemctl status obsidian-headless-x11vnc obsidian-headless
# View logs
sudo journalctl -u obsidian-headless -f
sudo journalctl -u obsidian-headless-x11vnc -f
```
# Connecting via VNC
## Port Forwarding
Forward port 5900 from the remote server to connect locally.
**Standard SSH**:
```bash
ssh -L 5900:localhost:5900 <user>@<host>
```
**Google Cloud**:
```bash
gcloud compute ssh <instance-name> --zone=<zone> -- -L 5900:localhost:5900
```
## VNC Client Options ([[macOS]])
**TigerVNC (Recommended)**:
```bash
# Install
brew install tiger-vnc
# Connect (use -SecurityTypes VncAuth for reliable authentication)
vncviewer -SecurityTypes VncAuth localhost:5900
```
**macOS built-in Screen Sharing**:
- Finder → `⌘+K` → `vnc://localhost:5900`
- Note: May have compatibility issues with x11vnc; TigerVNC is more reliable
## Troubleshooting VNC
If connection hangs or fails:
1. Reset the VNC password on the server:
```bash
x11vnc -storepasswd
sudo systemctl restart obsidian-headless-x11vnc.service
```
2. Use `-SecurityTypes VncAuth` flag with TigerVNC
3. Check x11vnc logs: `sudo journalctl -u obsidian-headless-x11vnc.service -f`
# CLI
The [[Obsidian]] CLI (v1.12+) can be enabled in Settings > General > Advanced. Once enabled, `obsidian help` lists all available commands.
The CLI works by briefly launching an [[Electron]] process that connects to the already-running headless instance via single-instance IPC. It sends the command, pipes the output back, and exits. This means:
- The headless Obsidian service **must be running** for the CLI to work
- The `DISPLAY` variable **must point to the Xvfb display** (`:5`) -- see [[#Global Display Configuration]]
- CLI commands can be run non-interactively over SSH, e.g.:
```bash
gcloud compute ssh <instance> --zone=<zone> -- obsidian vault
```