Skip to main content
By the end of this page you have ohd running as a service, an administrator account you created in the browser, and the browser extension, desktop app, or CLI syncing against your own server. No account with us, no cloud relay — the daemon makes zero outbound connections except ones you configure.

Install

Each release ships oh (the CLI) and ohd (the daemon) as self-contained executables — no Node.js required. The same binaries are downloadable from the releases page.
The install script verifies SHA-256 checksums and installs oh and ohd to ~/.local/bin:
Binaries currently ship for Apple silicon (mac-arm64). The service lifecycle below runs on launchd — the LaunchAgent starts at login.

Start the service

On Linux, install also enables the unit for boot and turns on user lingering, so the daemon survives reboots and outlives the SSH session that installed it; if a step needs privileges, the exact manual command is printed instead. On macOS the LaunchAgent starts at login.

Claim the server

A fresh server has no users and no administrator. The first browser to reach it creates one — open it on the machine that runs it:
The page asks for a name, an email and a password (at least 8 characters). That account becomes the server’s first administrator: it signs in from any browser and it is the one that reaches the admin console. Nothing is pasted into the browser — being on the server’s own machine is proof enough that you are its operator. From any other machine the claim also asks for a setup code. The daemon prints it at boot, and ohd status repeats it for as long as the server stays unclaimed:
The code is minted per run and never written to disk, so every restart replaces it. A stale code is refused exactly like a wrong one and the refusal cannot say which it was — if the daemon has restarted since you copied it, read the current one from ohd status. Once the claim succeeds the setup screen is gone for good: every later browser is asked to sign in, a second claim is refused, and further accounts are created from the admin console or provisioned through SSO.
There is no self-service password reset on the sign-in page. If the first administrator’s password is lost, reset it on the server with ohd user set-password <email> (daemon stopped).

Claiming a headless server

Most servers have no browser on them. Two ways in, in order of preference:
  • Forward loopback over SSH. ssh -L 8137:127.0.0.1:8137 you@server, then open http://127.0.0.1:8137/ on your own machine. The daemon sees a loopback peer and the browser sees a secure origin, so this needs no setup code and no TLS.
  • Put a TLS reverse proxy in front first, then claim at https://<your-host>/ with the setup code from ohd status — see LAN vs TLS proxy.
A plain LAN URL like http://<server>:8137/ cannot claim the server: the browser refuses to start the web app on a non-loopback HTTP origin at all (see below).

If the claim unpairs a device

Tokens minted with ohd show-token before the claim are unbound — they act as the server operator, which after a claim would be a way around the administrator you just created. So the claim revokes every one of them and the setup screen says how many devices it unpaired. Pair them again from the admin console.

Connect a client

A person signs in from their client on the server’s own page. In the browser extension or the desktop app, open Settings → Backup and Sync → Sign in to a server…, add the server’s address, and click Sign in on …: the server’s page opens, you sign in there with your email and password, approve the device, and the client is signed in as you. Those clients dial ws://<server>:8137 from any machine on the network, with no TLS involved — the normal way to use a headless server. For the CLI:
Machines — an MCP agent, a CI job — and devices you set up for someone else take a token instead, minted in the admin console under Settings → Backends → Open admin console → Paired devices and saved with oh connect --token <secret>. See Tokens & pairing. The daemon serves the Workbench as a web app on its bind, but a browser only runs it from a secure origin: http://127.0.0.1:8137/ on the server’s own machine, or https://… behind a TLS reverse proxy. A plain LAN URL like http://<server>:8137/ loads and then refuses to start — that is a browser rule (the Workbench needs crypto.subtle, which browsers withhold on non-loopback HTTP), and it applies to the web app only, not to the extension, desktop, or CLI clients. The CLI installs on any client machine — the same script as above on macOS and Linux (without --with-daemon), or the PowerShell script on Windows:

Reach it from the LAN

The daemon binds 127.0.0.1:8137 by default — loopback only. To make it LAN-reachable you must also say how the connection is protected: either a TLS-terminating reverse proxy in front, or an explicit acknowledgment that cleartext on a trusted network is acceptable:
Without one of the two, a 0.0.0.0 bind refuses to boot rather than serve auth tokens and pairing secrets unencrypted by accident. ohd install only persists the flags — a service that is already running keeps its old bind until ohd restart. With the LAN bind in effect and clients still unable to connect, they are being stopped before the daemon (which logs every connection it refuses, so a failure with no log line never reached it): check that the host firewall (ufw/firewalld) admits port 8137. For anything beyond a trusted LAN, terminate TLS at a reverse proxy — Caddy makes it two lines:
The full decision guide — with the nginx config and the --trusted-proxy semantics — is LAN vs TLS proxy.

Reconfigure at any time

daemon.json is the durable configuration. ohd install persists the flags it is given into it and may be re-run at any time to reconfigure — an omitted flag keeps its persisted value — and ohd restart applies the result. Runtime settings (like the MCP switches) are separate: