Skip to content

Connect OpenClaw Nodes: Setup Guide for Remote Devices

WS nodes use device pairing to stay secure. When a node connects, it presents a device identity, and the Gateway creates a pairing request with the role: node. You can approve these requests using the devices CLI or the UI.

Quick CLI commands to manage this:

Terminal window
openclaw devices list
openclaw devices approve <requestId>
openclaw devices reject <requestId>
openclaw nodes status
openclaw nodes describe --node <idOrNameOrIp>

If a node tries to reconnect with different auth details—like a new role, different scopes, or a changed public key—the old pending request is replaced by a new requestId. You should run openclaw devices list again before you approve the connection.

A few things to keep in mind:

  • The nodes status command only shows a node as paired if its device pairing role specifically includes node.
  • The node.pair.* commands (like openclaw nodes pending/approve/reject) manage a separate gateway-owned node pairing store. This store does not block the initial WebSocket connect handshake.

You should use a node host when your Gateway is running on one machine but you want your commands to execute on a different one. In this setup, the model still communicates with the gateway, and the gateway forwards exec calls to the node host whenever you select host=node.

The work is split between the machines to keep things organized:

  • Gateway host: This machine receives messages, runs the model, and routes tool calls to the right place.
  • Node host: This machine handles the actual execution of system.run or system.which.
  • Approvals: Security is handled on the node host via the ~/.openclaw/exec-approvals.json file.
  • Context: Approval-backed node runs are tied to the exact request context to prevent unauthorized changes.

Regarding execution safety: for direct shell or runtime file executions, OpenClaw tries to bind to one specific local file. If that file changes before execution, the run is denied. If OpenClaw cannot identify exactly one concrete local file for a command, it will deny the execution instead of guessing. In those cases, you should use sandboxing, separate hosts, or a trusted allowlist.

Run this on the machine you want to use as a node:

Terminal window
openclaw node run --host <gateway-host> --port 18789 --display-name "Build Node"

Remote gateway via SSH tunnel (loopback bind)

Section titled “Remote gateway via SSH tunnel (loopback bind)”

If your Gateway is set to bind to loopback (gateway.bind=loopback), which is the default in local mode, remote node hosts won’t be able to connect directly. You can get around this by creating an SSH tunnel and pointing the node host at the local end of that tunnel.

Example (node host -> gateway host):

Terminal window
# Terminal A (keep running): forward local 18790 -> gateway 127.0.0.1:18789
ssh -N -L 18790:127.0.0.1:18789 user@gateway-host
# Terminal B: export the gateway token and connect through the tunnel
export OPENCLAW_GATEWAY_TOKEN="<gateway-token>"
openclaw node run --host 127.0.0.1 --port 18790 --display-name "Build Node"

Auth details to remember:

  • openclaw node run works with either a token or a password.
  • Using environment variables like OPENCLAW_GATEWAY_TOKEN or OPENCLAW_GATEWAY_PASSWORD is the recommended approach.
  • The system falls back to gateway.auth.token or gateway.auth.password in your config.
  • In local mode, the node host ignores gateway.remote.token and gateway.remote.password.
  • In remote mode, those remote tokens are used according to standard precedence rules.
  • If local gateway.auth.* SecretRefs are set but can’t be resolved, the auth will fail for safety.
  • Node-host auth resolution specifically looks for OPENCLAW_GATEWAY_* env vars.

If you want the node host to run in the background as a service, use these commands:

Terminal window
openclaw node install --host <gateway-host> --port 18789 --display-name "Build Node"
openclaw node restart

Back on the gateway host, you’ll need to finalize the connection:

Terminal window
openclaw devices list
openclaw devices approve <requestId>
openclaw nodes status

If the node retries with new auth details, just run openclaw devices list again and approve the latest requestId. You can set the name using the --display-name flag when you first run or install the node, or rename it later from the gateway using openclaw nodes rename --node &lt;id|name|ip&gt; --name "Build Node".

Exec approvals are specific to each node host. You can add commands to the allowlist directly from the gateway:

Terminal window
openclaw approvals allowlist add --node &lt;id|name|ip&gt; "/usr/bin/uname"
openclaw approvals allowlist add --node &lt;id|name|ip&gt; "/usr/bin/sw_vers"

These approvals are stored on the node host in the ~/.openclaw/exec-approvals.json file.

To make the node your default for execution, update your gateway config:

Terminal window
openclaw config set tools.exec.host node
openclaw config set tools.exec.security allowlist
openclaw config set tools.exec.node "<id-or-name>"

You can also do this on a per-session basis by using the command:

/exec host=node security=allowlist node=<id-or-name>

Once this is configured, any exec call with host=node will run on that specific node host, provided the command is on the allowlist. You can find more details in the Node host CLI, Exec tool, and Exec approvals documentation.

If you need to perform raw RPC calls, you can use the low-level invoke command. This is useful for direct interaction with the node’s command surface.

Terminal window
openclaw nodes invoke --node <idOrNameOrIp> --command canvas.eval --params '{"javaScript":"location.href"}'

While this raw method works, there are higher-level helpers available for common tasks like sending media attachments to an agent.

When a node is displaying the Canvas (WebView), you can use canvas.snapshot to get the current view as a { format, base64 } object. There is a handy CLI helper that handles writing this to a temporary file and printing the MEDIA:<path> for you.

Terminal window
openclaw nodes canvas snapshot --node <idOrNameOrIp> --format png
openclaw nodes canvas snapshot --node <idOrNameOrIp> --format jpg --max-width 1200 --quality 0.9

You can also control the canvas remotely with these commands:

Terminal window
openclaw nodes canvas present --node <idOrNameOrIp> --target https://example.com
openclaw nodes canvas hide --node <idOrNameOrIp>
openclaw nodes canvas navigate https://example.com --node <idOrNameOrIp>
openclaw nodes canvas eval --node <idOrNameOrIp> --js "document.title"

A few notes on usage:

  • The canvas present command takes URLs or local file paths via --target. You can also use --x, --y, --width, and --height to position the window.
  • The canvas eval command accepts JavaScript either through the --js flag or as a positional argument.

For A2UI interactions, use the following commands to push or reset data:

Terminal window
openclaw nodes canvas a2ui push --node <idOrNameOrIp> --text "Hello"
openclaw nodes canvas a2ui push --node <idOrNameOrIp> --jsonl ./payload.jsonl
openclaw nodes canvas a2ui reset --node <idOrNameOrIp>

Note that currently only A2UI v0.8 JSONL is supported; v0.9 or createSurface calls will be rejected.

AI Setup Assistant

If you need to capture media from your nodes, the camera toolset is your best friend. You can quickly list available cameras or snap a photo using these commands:

Terminal window
openclaw nodes camera list --node <idOrNameOrIp>
openclaw nodes camera snap --node <idOrNameOrIp> # default: both facings (2 MEDIA lines)
openclaw nodes camera snap --node <idOrNameOrIp> --facing front

For video clips, use the clip command. You can specify the duration and choose whether to include audio:

Terminal window
openclaw nodes camera clip --node <idOrNameOrIp> --duration 10s
openclaw nodes camera clip --node <idOrNameOrIp> --duration 3000 --no-audio

There are a few things to keep in mind for a smooth experience:

  • The node must be foregrounded for canvas.* and camera.* calls. If it is in the background, you will see a NODE_BACKGROUND_UNAVAILABLE error.
  • Clip duration is currently capped at 60 seconds. This prevents base64 payloads from becoming too large to handle.
  • Android devices will ask for CAMERA and RECORD_AUDIO permissions. If these are denied, the command fails with *_PERMISSION_REQUIRED.

When you need to see exactly what is happening on a node’s display, you can use screen.record to generate an mp4 file. Here is how you run it:

Terminal window
openclaw nodes screen record --node <idOrNameOrIp> --duration 10s --fps 10
openclaw nodes screen record --node <idOrNameOrIp> --duration 10s --fps 10 --no-audio

A few tips for screen recording:

  • Availability depends on the specific node platform you are using.
  • Recordings are clamped to a maximum of 60 seconds.
  • You can use the --no-audio flag to stop microphone capture.
  • If the node has multiple displays, use the --screen <index> flag to pick the right one.

You can retrieve geographic coordinates from nodes using location.get, provided the feature is enabled in the settings.

The CLI helper makes this easy:

Terminal window
openclaw nodes location get --node <idOrNameOrIp>
openclaw nodes location get --node <idOrNameOrIp> --accuracy precise --max-age 15000 --location-timeout 10000

Keep these details in mind:

  • Location services are off by default for privacy.
  • “Always” tracking requires specific system permissions, and background fetching works on a best-effort basis.
  • The data you get back includes latitude, longitude, accuracy in meters, and a timestamp.

For Android nodes that support telephony, you can send text messages once the user grants the SMS permission.

You can trigger this using a low-level invoke command:

Terminal window
openclaw nodes invoke --node <idOrNameOrIp> --command sms.send --params '{"to":"+15555550123","message":"Hello from OpenClaw"}'

Before you try this, remember:

  • You must accept the permission prompt on the Android device itself before the node advertises this capability.
  • This won’t work on Wi-Fi-only devices. If there is no telephony support, sms.send will not be available.

Android nodes can show extra command families when you turn on the right capabilities. You can use these families to interact with device data:

  • device.status, device.info, device.permissions, device.health
  • notifications.list, notifications.actions
  • photos.latest
  • contacts.search, contacts.add
  • calendar.events, calendar.add
  • callLog.search
  • sms.search
  • motion.activity, motion.pedometer

Here is how you can invoke these commands:

Terminal window
openclaw nodes invoke --node <idOrNameOrIp> --command device.status --params '{}'
openclaw nodes invoke --node <idOrNameOrIp> --command notifications.list --params '{}'
openclaw nodes invoke --node <idOrNameOrIp> --command photos.latest --params '{"limit":1}'

Keep in mind that motion commands are gated by the sensors available on your specific device.

The macOS node gives you access to system.run, system.notify, system.execApprovals.get, and system.execApprovals.set. If you are using the headless node host, you get system.run, system.which, system.execApprovals.get, and system.execApprovals.set.

You can try these examples:

Terminal window
openclaw nodes notify --node <idOrNameOrIp> --title "Ping" --body "Gateway ready"
openclaw nodes invoke --node <idOrNameOrIp> --command system.which --params '{"name":"git"}'

Here are the details on how these system commands behave:

  • When you use system.run, the payload includes the stdout, stderr, exit code, and execution metadata.
  • Shell execution now moves through the exec tool with host=node. The nodes command stays as the direct-RPC surface for explicit node commands.
  • You won’t find system.run or system.run.prepare in nodes invoke; those are only available on the exec path.
  • system.notify follows the notification permission state set in the macOS app.
  • If a node has unknown platform or deviceFamily metadata, it uses a safe default allowlist that excludes system.run and system.which. If you need these for an unknown platform, add them yourself using gateway.nodes.allowCommands.
  • system.run supports several flags: --cwd, --env KEY=VAL, --command-timeout, and --needs-screen-recording.
  • For shell wrappers like bash, sh, or zsh, any request-scoped --env values are limited to a specific allowlist: TERM, LANG, LC_*, COLORTERM, NO_COLOR, and FORCE_COLOR.
  • For allow-always decisions in allowlist mode, dispatch wrappers such as env, nice, nohup, stdbuf, or timeout keep the inner executable paths. If unwrapping isn’t safe, no allowlist entry is saved automatically.
  • On Windows node hosts in allowlist mode, running shell wrappers via cmd.exe /c requires manual approval. An allowlist entry alone won’t auto-allow the wrapper form.
  • system.notify supports --priority &lt;passive|active|timeSensitive&gt; and --delivery &lt;system|overlay|auto&gt;.
  • Node hosts ignore PATH overrides and strip out risky startup or shell keys like DYLD_*, LD_*, NODE_OPTIONS, PYTHON*, PERL*, RUBYOPT, SHELLOPTS, and PS4. If you need extra PATH entries, configure the node host service environment or install tools in standard locations.
  • On macOS node mode, system.run is gated by the exec approvals in the macOS app (Settings → Exec approvals).
  • The Ask, allowlist, full, and denied behaviors work the same as the headless node host; denied prompts return SYSTEM_RUN_DENIED.
  • On the headless node host, system.run is gated by exec approvals stored in ~/.openclaw/exec-approvals.json.

When you have multiple nodes running, you can bind exec to one specific node. This sets your default for exec host=node, though you can still override this setting for individual agents if needed.

To set a global default for your setup, run this command:

Terminal window
openclaw config set tools.exec.node "node-id-or-name"

If you want a specific agent to use a different node, you can apply an override. First, check your agent list, then target the specific index:

Terminal window
openclaw config get agents.list
openclaw config set agents.list[0].tools.exec.node "node-id-or-name"

If you decide to let the system use any available node again, you just need to unset those configuration keys:

Terminal window
openclaw config unset tools.exec.node
openclaw config unset agents.list[0].tools.exec.node

Nodes can include a permissions map that you will see when using node.list or node.describe. This map is keyed by the specific permission name—for example, screenRecording or accessibility. A true value means the permission has been granted for that node.

If you don’t need a UI, you can run a headless node host. This version connects to the Gateway WebSocket and lets you use system.run and system.which. It is a great choice for Linux or Windows setups, or if you want to run a minimal node right next to your server.

To get it started, run this command:

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

Keep these points in mind:

  • You still need to pair the device. The Gateway will show a pairing prompt when you try to connect.
  • The node host saves your node id, token, display name, and gateway connection info in ~/.openclaw/node.json.
  • Local exec approvals are enforced through ~/.openclaw/exec-approvals.json (you can read more in the Exec approvals documentation).
  • On macOS, the headless node host runs system.run locally by default. You can set OPENCLAW_NODE_EXEC_HOST=app to route those commands through the companion app host. If you want the process to fail when the app host is missing, add OPENCLAW_NODE_EXEC_FALLBACK=0.
  • If your Gateway WebSocket uses TLS, make sure to add the --tls or --tls-fingerprint flags.
  • The macOS menubar app connects to the Gateway WS server as a node. This allows you to run openclaw nodes … against your Mac.
  • When you use remote mode, the app opens an SSH tunnel for the Gateway port and connects through localhost.
OpenClaw

OpenClaw Expert

Still stuck?

If this page didn't answer your case, ask OpenClaw Expert for step-by-step guidance.