openclaw node: Run a Headless Node Host

Learn how to run a headless node host with `openclaw node run` to execute commands on remote machines. Ideal for Linux/Windows boxes, CI nodes, or automation targets.

Read this when

  • Running the headless node host
  • Pairing a non-macOS node for system.run

openclaw node

Run a headless node host that links to the Gateway WebSocket and makes system.run / system.which available on this machine.

The menu bar app on macOS already bundles this node-host runtime into its own node connection and adds native Mac capabilities. Only use openclaw node run on a Mac when you deliberately want a headless node without the app. Running both results in two node identities for the same machine.

Why use a node host?

Choose a node host when you need agents to execute commands on other machines in your network without installing a full macOS companion app there.

Typical scenarios:

  • Execute commands on remote Linux/Windows boxes (build servers, lab machines, NAS).
  • Keep exec sandboxed on the gateway, but hand off approved runs to other hosts.
  • Offer a lightweight, headless execution target for automation or CI nodes.

Execution remains protected by exec approvals and per-agent allowlists on the node host, so command access stays scoped and explicit.

openclaw node run can publish plugin or MCP-backed tools after it connects. The Gateway trusts descriptors from the paired node by default, while requiring each descriptor's command to remain in the node's approved command surface. The agent sees each accepted descriptor as a normal plugin tool, but execution still goes through node.invoke, so disconnecting the node removes the tool from new agent runs. Gateway operators can disable publication with gateway.nodes.pluginTools.enabled: false.

For declarative MCP tools, add the normal MCP server shape under nodeHost.mcp.servers in openclaw.json on the node machine, then restart the node host. The node declares the approval-gated mcp.tools.call.v1 command family and publishes listed tools after connecting; changing the server list later does not require re-pairing. See Node-hosted MCP servers.

Browser proxy (zero-config)

Node hosts automatically advertise a browser proxy if browser.enabled is not disabled on the node. This lets the agent use browser automation on that node without extra configuration.

By default, the proxy exposes the node's normal browser profile surface. If you set nodeHost.browserProxy.allowProfiles, the proxy becomes restrictive: non-allowlisted profile targeting is rejected, and persistent profile create/delete routes are blocked through the proxy.

Disable it on the node if needed:

{
  nodeHost: {
    browserProxy: {
      enabled: false,
    },
  },
}

Run (foreground)

For one-paste onboarding, use openclaw connect. It accepts a single-use join URL or the same setup code forms as --pair, then runs this node-host runtime.

openclaw node run --host <gateway-host> --port 18789

Or paste a short-lived node setup link from the Control UI Devices page:

openclaw node run --pair "oc-pair://<setup-code>"

Options:

  • --host <host>: Gateway WebSocket host (default: 127.0.0.1)
  • --pair <code-or-url>: Read the Gateway endpoint, bootstrap token, TLS mode, and optional certificate pin from a setup code or oc-pair:// URL. Explicit gateway flags override values from --pair.
  • --port <port>: Gateway WebSocket port (default: 18789)
  • --context-path <path>: Gateway WebSocket context path (e.g. /openclaw-gw). Appended to the WebSocket URL.
  • --tls: Use TLS for the gateway connection
  • --no-tls: Force a plaintext Gateway connection even when the local Gateway config enables TLS
  • --tls-fingerprint <sha256>: Expected TLS certificate fingerprint (sha256)
  • --node-id <id>: Override the client instance ID stored in shared SQLite state (does not reset pairing)
  • --display-name <name>: Override the node display name
  • --share-installed-apps: On macOS, advertise installed applications through device.apps
  • --no-share-installed-apps: Disable installed application sharing

Gateway auth for node host

--pair uses a 10-minute single-use bootstrap token for the first connection. After pairing, reconnects use the durable device credential. The setup link does not pre-approve system.run; normal node approval and SSH verification remain in force. node install --pair is intentionally unavailable because a short-lived bearer setup link must not be persisted in service arguments.

openclaw node run and openclaw node install resolve gateway auth from config/env (no --token/--password flags on node commands):

  • OPENCLAW_GATEWAY_TOKEN / OPENCLAW_GATEWAY_PASSWORD are checked first.
  • Then local config fallback: gateway.auth.token / gateway.auth.password.
  • In local mode, node host intentionally does not inherit gateway.remote.token / gateway.remote.password.
  • If gateway.auth.token / gateway.auth.password is explicitly configured via SecretRef and unresolved, node auth resolution fails closed (no remote fallback masking).
  • In gateway.mode=remote, remote client fields (gateway.remote.token / gateway.remote.password) are also eligible per remote precedence rules.
  • Node host auth resolution only honors OPENCLAW_GATEWAY_* env vars.

For a Gateway protected by Cloudflare Access, combine CF_ACCESS_CLIENT_ID with CF_ACCESS_CLIENT_SECRET prior to running openclaw connect, openclaw node run, or openclaw node install. The node persists env SecretRefs under its canonical gateway.cloudflareAccess.clientId and clientSecret connection keys. Installed services retain those values in the managed service environment file, rather than in service arguments or inline supervisor definitions. Access credentials demand HTTPS/WSS; plaintext HTTP/WS fails before SecretRef resolution, while credential-free plaintext node routes stay unchanged. See Gateway deployments that cannot host nodes.

When a node targets a plaintext ws:// Gateway, loopback, private IP literals, .local, and Tailnet *.ts.net hosts are permitted. For any other trusted private-DNS name, OPENCLAW_ALLOW_INSECURE_PRIVATE_WS=1 must be set; otherwise node startup fails closed and directs you to wss://, an SSH tunnel, or Tailscale. This opt-in lives in the process environment, not in an openclaw.json config key. openclaw node install writes it into the supervised node service when it appears in the install command environment.

Service (background)

Deploy a headless node host as a user service (launchd on macOS, systemd on Linux, Windows Task Scheduler on Windows).

openclaw node install --host <gateway-host> --port 18789

Options:

  • --host <host>: Gateway WebSocket host (default: 127.0.0.1)
  • --port <port>: Gateway WebSocket port (default: 18789)
  • --context-path <path>: Gateway WebSocket context path (e.g. /openclaw-gw). Appended to the WebSocket URL.
  • --tls: Use TLS for the gateway connection
  • --no-tls: Force a plaintext Gateway connection even when the local Gateway config enables TLS
  • --tls-fingerprint <sha256>: Expected TLS certificate fingerprint (sha256)
  • --node-id <id>: Override the client instance ID stored in shared SQLite state (does not reset pairing)
  • --display-name <name>: Override the node display name
  • --share-installed-apps: On macOS, advertise installed applications through device.apps
  • --no-share-installed-apps: Disable installed application sharing
  • --runtime <node|bun>: Service runtime (default: node). Bun 1.4+ with WAL-reset-safe node:sqlite is an explicit opt-in; Node remains recommended.
  • --force: Reinstall/overwrite if already installed

Point OPENCLAW_WRAPPER at an executable wrapper file to replace the chosen runtime and CLI entrypoint. The wrapper gets node run plus the connection arguments; it must start OpenClaw and pass those arguments along.

If installation flags a runtime probe failure, inspect the executable and working directory named in the error. For instance, when switching users with runuser, move to a directory the target user can read first. A failed probe does not indicate the installed Node version is unsupported; upgrade guidance is reserved for missing or unsupported runtimes.

Linux (systemd user service): Execute sudo loginctl enable-linger <user> after install. Without lingering, systemd --user stops the node service when your last SSH session closes, so the node quietly goes offline after logout. openclaw node install emits this warning when it sees lingering is disabled.

Manage the service:

openclaw node status
openclaw node start
openclaw node stop
openclaw node restart
openclaw node uninstall

Choose openclaw node run for a foreground node host (no service).

Service commands accept --json for machine-readable output. node start and node restart output install hints and exit nonzero when no managed node service is installed; run openclaw node install first. Stopping an absent service remains a successful no-op.

The node host retries Gateway restart and network closes in-process. If the Gateway reports a terminal token/password/bootstrap auth pause, the node host logs the close detail and exits non-zero so launchd/systemd/Task Scheduler can restart it with fresh config and credentials. Pairing-required pauses stay in the foreground flow so the pending request can be approved.

Pairing

The first connection creates a pending device pairing request (role: node) on the Gateway.

When the Gateway host can SSH to the node host non-interactively (same user, trusted host key), the pending request is approved automatically: the Gateway runs openclaw node identity --json on the node host over SSH and approves on an exact device-key match. This is on by default; see SSH-verified device auto-approval for requirements and how to disable it (gateway.nodes.pairing.sshVerify: false).

Otherwise approve manually via:

openclaw devices list
openclaw devices approve <requestId>

Inspect the local node identity the Gateway verifies against:

openclaw node identity --json

It prints the device ID and public key from the primary row in state/openclaw.sqlite and never creates the database or a new identity.

On tightly controlled node networks, the Gateway operator can explicitly opt in to auto-approving first-time node pairing from trusted CIDRs:

{
  gateway: {
    nodes: {
      pairing: {
        autoApproveCidrs: ["192.168.1.0/24"],
      },
    },
  },
}

This is disabled by default (autoApproveCidrs is unset). It only applies to fresh role: node pairing with no requested scopes, from a client IP the Gateway trusts. Operator/browser clients, Control UI, WebChat, and role, scope, metadata, or public-key upgrades still require manual approval.

When a node retries pairing with altered authentication details, such as a different role, scope, or public key, the earlier pending request gets replaced and a fresh requestId is generated. Before you approve it, execute openclaw devices list once more.

Identity and pairing state

The headless node keeps its client instance ID separate from the signed device identity that the Gateway relies on for pairing and routing. That data resides in the OpenClaw state directory, which defaults to ~/.openclaw, or $OPENCLAW_STATE_DIR if configured:

StatePurpose
state/openclaw.sqlite (config_machine_state, key nodeHost.config)Client instance ID, display name, and Gateway connection metadata. The client sends this ID as instanceId.
state/openclaw.sqlite (device_identities, primary)Signed Ed25519 keypair and derived device ID. For signed connections, this device ID is the routed node ID and pairing identity.
state/openclaw.sqlite (device_auth_tokens)Paired device tokens, keyed by cryptographic device ID and role.

Running --node-id only updates the client instance ID within the shared SQLite state. It leaves the cryptographic device ID untouched and does not clear pairing authentication. Likewise, migrating a retired node.json with openclaw doctor --fix fails to reset pairing. To revoke and re-pair a node, follow these steps:

  1. On the Gateway, run openclaw nodes remove --node <id|name|ip>.
  2. On the node, restart the installed service with openclaw node restart, or stop and rerun the foreground openclaw node run command. That action triggers the device-pairing flow. If openclaw devices list shows no request and the node reports AUTH_DEVICE_TOKEN_MISMATCH, restart or rerun it once more. The rejected attempt clears the now-revoked local token, allowing the next attempt to request pairing.
  3. On the Gateway, run openclaw devices list, then openclaw devices approve <deviceRequestId>.
  4. Restart or rerun the node again. A client paused for pairing does not resume automatically after approval; this reconnect creates the separate command-surface request.
  5. On the Gateway, run openclaw nodes pending, then openclaw nodes approve <nodeRequestId>.

The two request IDs differ from each other. A trusted-CIDR policy that applies can auto-approve the initial device-pairing step, but command-surface approval is still checked separately.

In older OpenClaw releases, node-host state was stored in node.json, the signed identity in identity/device.json, and paired auth in identity/device-auth.json. Stop the node host and run openclaw doctor --fix once. Doctor claims each retired source, validates it, imports and verifies the canonical SQLite row, then deletes the old file. While either retired file or an interrupted Doctor claim remains, normal node commands fail closed with this repair instruction. Keep state/openclaw.sqlite private, since it holds the device keypair and auth tokens.

Exec approvals

Local exec approvals gate system.run:

  • $OPENCLAW_STATE_DIR/state/openclaw.sqlite#exec_approvals_config, or ~/.openclaw/state/openclaw.sqlite#exec_approvals_config when the variable is unset
  • Exec approvals
  • openclaw approvals --node <id|name|ip> (edit from the Gateway)

For approved async node exec, OpenClaw prepares a canonical systemRunPlan before prompting. The later approved system.run forward reuses that stored plan, so edits to command/cwd/session fields after the approval request was created are rejected instead of changing what the node executes.

2,008 words · updated Sep 1, 2026