sotashimozono1k downloadsEdit remote vaults over SSH/SFTP — VS Code Remote-SSH style.
Edit an Obsidian vault that lives on a remote server — over plain SSH. Think of VS Code's Remote-SSH, but for Obsidian: open a vault that sits on your home server, VPS, work box, or cluster, and edit it from a real Obsidian window. Files, attachments, search, and live updates — all served straight from the remote, over your own SSH connection.
No cloud. No sync service. No full copy left on your laptop. Your notes stay on the machine you control; this one only ever holds what you're actively looking at.

End-to-end demo, recorded in CI by demo-capture.yml — connect to a remote host and edit its vault from a normal Obsidian window.
Obsidian → Settings → Community plugins → Browse → search "Remote SSH" → Install → Enable.
That's the whole install. The first time you connect with the (recommended) RPC transport, the plugin fetches the matching helper daemon from its signed GitHub release — with a one-time confirmation, verified by sha256 — and uploads it to your host for you. Prefer no remote-side binary? Stay on the SFTP transport and skip the daemon entirely.
BRAT auto-installs and auto-updates pre-release builds straight from this repo:
sotashimozono/obsidian-remote-ssh → leave version blank.Download main.js, manifest.json, and styles.css from the
Releases page,
drop them into <your-vault>/.obsidian/plugins/remote-ssh/, and enable the
plugin. (The daemon still auto-downloads on first RPC connect; for an
air-gapped remote, grab the matching obsidian-remote-server-<os>-<arch>
binary from the same release and place it in …/remote-ssh/server-bin/.)
You already keep your notes — or your code, or your research — on a machine that isn't this one. A home server. A cloud VPS. The lab cluster. You SSH in all the time. You'd love to use Obsidian on those files without copying the whole vault down to every laptop and phone, and without handing it to a sync provider.
That's the entire point of this plugin:
ProxyJump bastion — all read from the same ~/.ssh/config your
terminal already uses. No account to create, no third-party server to trust.rsync, no Dropbox dance.git can keep operating on the canonical files; you're
editing the same files, not a copy that races those writers.fs.watch; if two saves collide, a 3-way merge UI (ancestor / mine /
theirs) opens instead of silently clobbering.About 3 minutes if you already have an SSH host.
privateKey / password / agent), transport
(RPC recommended), and the remote vault path (relative paths resolve
under $HOME — notes/main → ~/notes/main).To leave: close the shadow window, or run Remote SSH: Disconnect inside it. Your original window is never touched.
First time connecting a brand-new profile? Obsidian only learns about the new vault at startup, so the very first connect may need one restart of Obsidian before the window appears — then reconnect. It's a one-time step per profile; every later connect opens immediately.
| Feature | Notes |
|---|---|
| 🪟 Opens in its own Obsidian window | The window you started from is untouched; the remote vault is a first-class window with its own File Explorer, search, and command palette. |
| ⚡ Sub-second cold-open, even for 10k-file vaults | A single fs.walk RPC fetches the whole tree in one round-trip (per-folder fs.list fallback for SFTP). |
| 🖼️ Image / PDF / video rendering | A local-only HTTP bridge (random localhost port + bearer token) streams binary content to the Obsidian webview; images go through a fs.thumbnail cache (RPC transport). |
| 🔁 Live multi-client sync | fs.watch notifies every connected client when another writer changes a file; explorer + open editors update within ~1 s. |
| 🪢 3-way conflict resolution | If the remote file moved under your edit, a merge modal opens with ancestor / mine / theirs panes (plain text; binaries fall back to a 2-choice modal). |
| 📥 Offline write queue | Writes during a disconnect spool to a local queue and drain automatically on reconnect; the status bar shows the pending count. |
| 🩹 Automatic reconnect with backoff | SSH drops trigger a retry loop (default 5, exponential backoff to 30 s); reads come from cache during retries, writes spool to the queue. |
🔌 Jump host / ProxyJump |
Multi-hop SSH through bastions, from the same ~/.ssh/config your terminal uses. |
| 🔐 Signed daemon binaries | Every release daemon is Sigstore-cosign-signed; the plugin also runs a sha256 round-trip check on every deploy and refuses a mismatch. |
Most "edit my vault from anywhere" options copy the vault around —
Obsidian Sync (managed, encrypted, on Obsidian's servers), file-sync tools
(Syncthing / Dropbox / iCloud), or obsidian-git (a git repo you pull/push).
Each is great when replicating the vault is what you actually want.
This plugin is for the opposite case: the vault should live only on a remote host you control, with no second copy and no third party — and you want to edit it in place. If that's your situation, this is the niche it's built for; if not, one of the above is simpler.
Full breakdown (when to pick which): docs → comparison.
By design, the canonical vault never leaves your SSH host. On the local side:
| Capability | Why it's needed | Scope |
|---|---|---|
| Network | The SSH/SFTP connection | Only the host / port / optional jump host in your profile |
| Filesystem (outside the vault) | Read your SSH key + ~/.ssh/config to authenticate; cache the daemon binary |
~/.ssh/* and the plugin's own server-bin/ cache only |
| System identity | Sensible defaults for the client id + remote username | os.hostname() / os.userInfo().username (both overridable); SSH_AUTH_SOCK etc. to find your agent/config |
| Process execution | Start the daemon on the remote over SSH | Remote host only — no local shell execution |
| Clipboard (write) | The "Copy diagnostics" button | Only on explicit click; the clipboard is never read |
No telemetry, no analytics. Locally you keep only a working cache of the files you've opened — never a full replica of the vault.
Daemon binaries are signed with Sigstore cosign
(keyless OIDC); verify any release binary yourself with the one-line
cosign verify-blob in SECURITY.md.
On every deploy the plugin re-checks the uploaded binary by sha256 and refuses
to start a mismatch.
To report a vulnerability, use a private GitHub Security Advisory — policy in SECURITY.md. Please don't open a public issue for security bugs.
The shadow vault patches app.vault.adapter, so plugins that go through
Obsidian's vault API work transparently — Dataview, Templater, Daily Notes,
Tasks, Calendar, Excalidraw (RPC transport), and most others. Plugins that
bypass the adapter (importing Node fs directly, e.g. Omnisearch's
indexer) see an empty local directory instead of your remote vault.
Full matrix: docs → plugin compatibility.
The console log is the first thing to check (JSONL, one event per line):
~/.obsidian-remote/vaults/<profile-id>/.obsidian/plugins/remote-ssh/console.log
jq 'select(.level == "error")' console.log # just the errors
tail -20 console.log | jq -c '{ts, level, msg}'
A few common ones:
RPC transport; check the
profile's transport setting.ssh <host> 'cat ~/.obsidian-remote/server.log'.More: docs → troubleshooting.
Obsidian doesn't expose a public way to rebuild the vault model from a different storage adapter mid-session, so the plugin opens a separate shadow window whose vault is constructed from the remote tree at startup — every plugin in that window sees a normal-looking vault from frame zero.
flowchart LR
subgraph Original["Original window"]
U[Click Connect on profile P] --> M[ShadowVaultManager]
end
M --> B["ShadowVaultBootstrap<br/>materialise ~/.obsidian-remote/vaults/<P>/<br/>install plugin · seed autoConnectProfileId=P"]
B --> R["ObsidianRegistry<br/>register vault in obsidian.json"]
R --> W["WindowSpawner<br/>obsidian://open?path=…"]
W -.spawn.-> L
subgraph Shadow["Shadow window"]
L[onLayoutReady → connect to P] --> P[Patch app.vault.adapter → remote FS client]
P --> WK[fs.walk RPC / fs.list fallback]
WK --> V[VaultModelBuilder → File Explorer]
end
subgraph Transport["SSH transport"]
P -. fs ops .-> RPC[RpcRemoteFsClient]
P -. or .-> SFTP[SftpRemoteFsClient]
RPC --> D[(Go daemon · framed JSON-RPC over a unix-socket stream)]
SFTP --> SSH[(ssh2 SFTP)]
D --> FS[(Remote vault files)]
SSH --> FS
end
Two transports per profile:
RPC (recommended). A small Go daemon (obsidian-remote-server) runs on
the remote; the plugin auto-fetches the signed binary, uploads it, and starts
it. Vault FS ops flow as length-framed JSON-RPC over a forwarded unix socket.
Required for image/PDF/video rendering, sub-second cold-open, thumbnails, and
live fs.watch updates.SFTP. Direct SFTP over ssh2, no remote-side install — loses the
daemon-only features above.Design deep-dives: shadow-vault architecture · performance · conflicts & offline queue.
Contributions welcome — dev setup, branch + commit conventions, and how to run the test suite are in CONTRIBUTING.md. Bugs and feature requests: Issues.
Inspired by VS Code's Remote-SSH; the wire format is LSP-style framed JSON-RPC over a unix-socket-forwarded stream — the same shape language servers use, just for filesystem ops.
Remote SSH is, for now, maintained by a single individual. Sponsoring it helps sustain the continued maintenance and ongoing issue responses over time, which genuinely matters for a one-person project. Hugely appreciated 🙏
(A donate link also lives right in the plugin's settings page.)
Released and available in the Obsidian Community Plugins store. Daemon binaries are cosign-signed and end-to-end tested against Linux + macOS remotes.