On this page
On this page
Remote access
Remote access
OpenClaw runs one Gateway (the master) on a host and connects every client to it. The Gateway owns sessions, auth profiles, channels, and state; everything else is a client.
- Operators (you, or the macOS app): direct LAN/Tailnet WebSocket is simplest when the Gateway is reachable; SSH tunneling is the universal fallback.
- Nodes (iOS/Android and other devices): connect to the Gateway WebSocket (LAN/tailnet or SSH tunnel).
Remote clients can continue the same Gateway-owned conversation by URL or short reference. See Session synchronization and attachment.
OpenClaw ID from a source checkout
Maintainers running from main must build the local iroh transport before using
OpenClaw ID. From the repository root, run:
Install rustup and your platform's native build tools first
(a C/C++ toolchain and linker; Xcode Command Line Tools on macOS). The crate pins
Rust 1.99.0, matching its CI builds, without changing your default toolchain.
If your shell sets RUSTUP_TOOLCHAIN, unset it for this command so rustup uses
the crate pin.
The command writes the native .node file beside
crates/openclaw-iroh-node/index.js and skips compilation when it is newer than
the crate's sources and build inputs. Run it again after pulling native changes.
It is opt-in: pnpm build does not compile Rust. Restart your source Gateway
after building to use the binding.
An installed build without the transport cannot enable OpenClaw ID remote access; the Gateway keeps running and other access methods remain available.
OpenClaw ID nodes (Labs)
With OpenClaw ID enabled in Labs, headless nodes can reach a claw through its relay and encrypted tunnel, without Tailscale, an OpenClaw ID account, or a shared Gateway token. Each node generates and keeps its own tunnel key. The owner registers a signed, single-use node invitation; the link defaults to 10 minutes and cannot last longer than one hour.
From an approved owner browser, open Access → Nodes → Add node, enter a
label, and choose Create node command. Copy the command into a POSIX shell on
the computer you want to add. The default command uses the Gateway's OpenClaw
version through npx and installs the node as a service after approval. Expand
Already have OpenClaw installed? for the installed CLI command.
The browser builds the secret-bearing link and both commands locally. Neither the
link nor the secret is sent to the Gateway, stored by the UI, or logged. Closing
the dialog discards the command; pending requests remain available under Nodes.
If Discovery is unconfigured, no command is created: configure
gateway.openclawId.discoveryUrl on the Gateway first. The relay must be running.
Run the join command supplied by the owner, including the deployment's Discovery origin:
Treat the join link as a secret. The node terminal and the owner's approval dialog show the same six-digit verification code. Compare all six digits, review the self-reported name, platform, commands, and capabilities, then explicitly approve. An open dialog never approves a node automatically. The Discovery service can relay a pending request but cannot admit the node. Select Codes match — approve node only after comparing the terminal code and reviewing the complete declared surface. Sensitive commands and capabilities are highlighted. Deny node grants no access; ask the node operator to stop the waiting command.
The Nodes list shows each node's name, platform, last seen, and approving owner device. Remove node explains and confirms disconnection and deletion of the node pairing. Removing only a pairing in Devices leaves its OpenClaw ID key admitted but makes subsequent pairing manual. Members and viewers cannot manage nodes.
An admitted node key grants only role: "node" with client mode node. It grants
no owner or member identity, operator scopes, plugin cookies, or operator HTTP
access. Session-hosting nodes continue to authenticate their worker WebSocket and
HTTP transfers with those routes' separate credentials.
See node pairing for the one-time pairing approval and removal behavior. Creating or signing node invitations on the Gateway host's command line is not supported by this flow; an owner device or recovery root signs admission.
The core idea
The Gateway WebSocket binds to loopback by default, on port 18789 (gateway.port). For remote use, either expose it through Tailscale Serve / a trusted LAN-Tailnet bind, or forward the loopback port over SSH.
Topology options
| Setup | Where the Gateway runs | Best for |
|---|---|---|
| Always-on Gateway in your tailnet | Persistent host (VPS or home server), reached via Tailscale or SSH | Laptops that sleep often but need the agent always-on. See exe.dev (easy VM) or Hetzner (production VPS). |
| Home desktop | Desktop; laptop connects remotely via the macOS app's remote mode (Settings → Connection → OpenClaw runs) | Keeping the agent on hardware that stays powered on. Runbook: macOS remote access. |
| Laptop | Laptop, exposed safely via SSH tunnel or Tailscale Serve (keep gateway.bind: "loopback") |
Single-machine setups. See Tailscale and Web. |
For the always-on and laptop setups, prefer keeping gateway.bind: "loopback" and using Tailscale Serve for the Control UI, or a trusted LAN/Tailnet bind with gateway.remote.transport: "direct". SSH tunnel is the fallback that works from any machine.
Application previews need their own private ingress. A tunnel that forwards only the Gateway port does not forward portals. Use managed private Serve or wildcard portal ingress; the browser and application must use the service's returned portal URLs without replacing their host or port.
OpenClaw ID alongside existing access
OpenClaw ID remote access is an additional entrance, not a replacement for Tailscale, Cloudflare Access, or the Gateway's token/password. Each entrance keeps its own authentication and permissions:
- OpenClaw ID tunnel requests use the admitted device's owner, operator, or viewer role. Proxy identity headers and other Gateway credentials cannot widen that role. Viewers remain read-only; members cannot manage People.
- Tailscale Serve and Cloudflare Access users keep their existing Gateway
permissions. Arriving through a proxy on
127.0.0.1does not make them the local machine owner. A direct tailnet connection is remote too. - Turning on remote access requires an authenticated direct-loopback connection without proxy or identity headers. People, owner devices, and invites are available to that local operator or an active OpenClaw ID owner device, not merely to any proxy-authenticated administrator. Recovery-kit secrets are generated and exported in the browser, not downloaded from the Gateway.
Removing someone in People revokes their OpenClaw ID access only. It closes their OpenClaw ID connections, cancels their active or queued runs from those connections, and revokes the associated device tokens. It does not remove them from your tailnet or Cloudflare Access policy, or rotate a shared Gateway token or password. Revoke those independent permissions separately if they must lose all access. Likewise, someone independently given a stronger credential can still use its permissions through its own entrance.
A headerless SSH port forward is indistinguishable from a local process to the Gateway. Treat SSH access to the host as local-machine authority; do not expose an unmarked TCP forward to untrusted users.
Command flow (what runs where)
One Gateway owns state and channels; nodes are peripherals. Example (Telegram message routed to a node tool):
- Telegram message arrives at the Gateway.
- Gateway runs the agent, which decides whether to call a node tool.
- Gateway calls the node over the Gateway WebSocket (
node.invokeRPC). - Node returns the result; Gateway replies to Telegram.
Nodes do not run the Gateway service. Only one Gateway should run per host unless you intentionally run isolated profiles (see Multiple gateways). macOS app "node mode" is just a node client over the Gateway WebSocket.
SSH tunnel (CLI + tools)
With the tunnel up, openclaw health and openclaw status --deep reach the remote Gateway via ws://127.0.0.1:18789. openclaw gateway status, openclaw gateway health, openclaw gateway probe, and openclaw gateway call can also target a forwarded URL via --url.
To replace per-client SSH tunnels with one private wss:// endpoint while keeping the Gateway on loopback, follow Give your Gateway a stable HTTPS URL.
CLI remote defaults
Persist a remote target so CLI commands use it by default:
For a manually managed SSH tunnel, keep the URL at ws://127.0.0.1:18789 and open
the tunnel first. For a client-managed tunnel, set gateway.remote.sshTarget
(user@host or user@host:port); gateway.remote.url stays the local tunnel URL.
The macOS app uses the same settings. If the remote port differs from the local
one, set gateway.remote.remotePort.
When the configured loopback remote URL has gateway.remote.sshTarget and the
transport is not direct, CLI clients own the SSH tunnel, just as the macOS app does. They
cache paired-device credentials for the selected SSH target and remote Gateway
port, independently of the allocated local port. Set gateway.remote.remotePort
when the remote Gateway port differs from the port in the URL. TUI/RPC clients
and diagnostic checks share that credential scope; after pairing, diagnostics
do not require a shared token or password on every connection. The client closes
its tunnel on shutdown and cannot reconnect through a released forwarding port.
Existing configurations with sshTarget adopt this client-managed route on
upgrade. Set gateway.remote.transport: "direct" to retain a manually managed
forward instead.
Pinned wss:// loopback endpoints use a credential scope that also includes the
certificate fingerprint. Unidentified, manually forwarded loopback URLs cannot
safely reuse a device token saved only for that URL: the same port may now lead
to another Gateway. Configure the SSH target or TLS pin and enroll the selected
route using gateway.remote.token / gateway.remote.password, then approve
pairing on that Gateway. Historical URL-only entries are left untouched, never
silently reassigned to the new route. CLI and environment URL overrides retain
the selected listener instead of starting the configured SSH tunnel, even when
the URLs match. A CLI --url still follows the explicit credential rules above.
SSH aliases and their OpenSSH configuration remain
operator-owned route selections, not cryptographic Gateway identifiers.
Reassigning an enrolled SSH alias keeps its saved-credential scope, so its device
token can be sent to the newly selected destination. Use a new alias when
connecting to a different Gateway.
Local diagnostics prefer their local paired-device credential; an origin-cache fallback must match the local Gateway's pairing record. Non-loopback remote checks retain their existing exact-origin cache. These changes use the existing credential tables without adding a schema migration. Reverting just the route binding leaves both credential sets intact. If an older binary rejects an independently upgraded database schema, restore compatible pre-update state; retained token rows alone are not a database downgrade.
Running openclaw configure --section gateway or interactive onboarding again
preserves the remote TLS fingerprint and transport settings when you keep the
same URL (ignoring surrounding whitespace). Changing the URL clears those
endpoint settings. A newly confirmed discovery fingerprint replaces the saved
pin only for the discovered URL, and your selected auth method still applies.
Accepting a discovered direct connection selects direct transport. Choosing a
discovered SSH tunnel clears saved transport settings for the suggested loopback
URL, which may now reach a different host; start the displayed tunnel manually.
The onboarding and configure readiness checks use the saved TLS fingerprint for that same endpoint. Checking a different URL does not inherit its certificate pin.
Host-key verification is strict by default (gateway.remote.sshHostKeyPolicy: "strict"). Set it to "openssh" to delegate to your effective OpenSSH config instead; review your user and system SSH settings before enabling it.
For a Gateway already reachable on a trusted LAN or Tailnet, use direct mode:
Gateway behind an identity-aware proxy
To deploy this way from scratch — tunnel, Access application, Gateway trusted-proxy auth, and node routes — see Cloudflare Tunnel and Access. This section covers only the client side: how a CLI, TUI, or app authenticates to that edge.
Use gateway.remote.edgeAuth when an identity-aware proxy must authenticate the
WebSocket upgrade before traffic reaches the Gateway. Header values are
SecretInput fields, so they can come from env, file, exec, or store
secret providers without placing credentials directly in the config.
For Cloudflare Access, a generic exec secret provider can obtain a short-lived
application token from an operator-installed cloudflared binary:
secrets.providers.*.command must be an absolute path; replace
/usr/local/bin/cloudflared with the real, non-symlink install location on your
host, such as the resolved executable under a Homebrew prefix, and keep
trustedDirs pointing at the directory that actually holds it.
Exec providers run with a scrubbed environment. cloudflared reads its cached
application token from the user's home directory, so passEnv: ["HOME"] is
required; without it the provider exits non-zero and no header is produced.
Run cloudflared access login <gateway-url> once first so a token exists to
read.
For a Cloudflare Access service token, provide the two fixed headers from any supported secret provider. This example reads them from environment-backed SecretRefs:
OpenClaw's Gateway connection code never runs cloudflared itself and has no
Cloudflare dependency or login flow. Only the generic exec secret provider
invokes the exact command an operator configures. Resolved edge-auth headers are
sent only when the target matches the configured gateway.remote.url scope,
only over wss://, and never across redirects.
Credential precedence
Gateway credential resolution follows one shared contract across call/check/status paths and Discord exec-approval monitoring. Node-host uses the same contract with one local-mode exception (it ignores gateway.remote.*).
- Explicit credentials (
--token,--password, or a tool'sgatewayToken) always win on call paths that accept explicit auth. - URL override safety:
- CLI
--urlnever reuses implicit config/env credentials. - Env
OPENCLAW_GATEWAY_URLmay use env credentials only (OPENCLAW_GATEWAY_TOKEN/OPENCLAW_GATEWAY_PASSWORD).
- CLI
- Local mode defaults:
- token:
gateway.auth.token->OPENCLAW_GATEWAY_TOKEN->gateway.remote.token(remote fallback only when the local token is unset) - password:
gateway.auth.password->OPENCLAW_GATEWAY_PASSWORD->gateway.remote.password(remote fallback only when the local password is unset)
- token:
- Remote mode defaults:
- token:
gateway.remote.token->OPENCLAW_GATEWAY_TOKEN->gateway.auth.token - password:
OPENCLAW_GATEWAY_PASSWORD->gateway.remote.password->gateway.auth.password
- token:
- Node-host local-mode exception: environment credentials stay first and
gateway.remote.token/gateway.remote.passwordare ignored because node commands target an explicit host and port. - Remote startup/status/wizard checks with SecretRef support treat configured
gateway.remote.tokenandgateway.remote.passwordas authoritative for the configured target. Ambient environment credentials are considered only when neither remote credential is configured. If a configured remote SecretRef cannot be resolved, the check warns and does not fall back to environment credentials; a separately configured sibling credential that resolves successfully remains usable. - Gateway env overrides use
OPENCLAW_GATEWAY_*only.
Chat UI remote access
WebChat has no separate HTTP port; the SwiftUI chat UI connects directly to the Gateway WebSocket.
- Forward
18789over SSH (see above), then connect clients tows://127.0.0.1:18789. - For LAN/Tailnet direct mode, connect clients to the configured private
ws://or securewss://URL. - On macOS, the app's remote mode manages the selected transport automatically.
macOS app remote mode
The macOS menu bar app drives the same setup end-to-end: remote status checks, WebChat, and Voice Wake forwarding. Runbook: macOS remote access.
Security rules (remote/VPN)
Keep the Gateway loopback-only unless you are sure you need a bind.
- Loopback + SSH/Tailscale Serve is the safest default (no public exposure).
- Plaintext
ws://is accepted for loopback, private/LAN (RFC 1918), link-local, CGNAT,.local, and.ts.nethosts. Public remote hosts must usewss://. - Non-loopback binds (
lan/tailnet/custom, orautowhen loopback is unavailable) must use Gateway auth: token, password, or an identity-aware reverse proxy withgateway.auth.mode: "trusted-proxy". gateway.remote.token/.passwordare client credential sources; they do not configure server auth by themselves.- Local call paths can use
gateway.remote.*as a fallback only whengateway.auth.*is unset. - If
gateway.auth.token/gateway.auth.passwordis explicitly configured via SecretRef and unresolved, resolution stops with an error instead of falling back to remote credentials. gateway.remote.tlsFingerprintpins the remote TLS cert forwss://, including both operator/control traffic and the companion node in macOS direct mode. Without a stored pin, macOS pins on first use only after normal system trust passes; self-signed or private-CA Gateways need an explicit fingerprint or Remote over SSH.- Tailscale Serve can authenticate Control UI/WebSocket traffic via identity headers when
gateway.auth.allowTailscale: true. HTTP API endpoints do not use that header auth and instead follow the Gateway's normal HTTP auth mode. This tokenless flow assumes the Gateway host is trusted; set it tofalsefor shared-secret auth everywhere. - Trusted-proxy auth expects a non-loopback identity-aware proxy by default. Same-host loopback reverse proxies require explicit
gateway.auth.trustedProxy.allowLoopback = true. - Treat browser control like operator access: tailnet-only plus deliberate node pairing.
Deep dive: Security.
macOS: persistent SSH tunnel via LaunchAgent
For macOS clients, the easiest persistent setup uses an SSH LocalForward config entry plus a LaunchAgent that keeps the tunnel alive across reboots and crashes.
Step 1: add SSH config
Edit ~/.ssh/config:
Replace <REMOTE_IP> and <REMOTE_USER> with your values.
Step 2: copy SSH key (one-time)
Step 3: configure the gateway token
The Gateway accepts its configured secret in either field: gateway.remote.token or gateway.remote.password both work, including for password-mode Gateways. The server's gateway.auth.mode selects which configured secret to use. OPENCLAW_GATEWAY_TOKEN is still valid as a shell-level override, but the durable remote-client setup is gateway.remote.token / gateway.remote.password.
Step 4: create the LaunchAgent
Save as ~/Library/LaunchAgents/ai.openclaw.ssh-tunnel.plist:
Step 5: load the LaunchAgent
The tunnel starts automatically at login, restarts on crash, and keeps the forwarded port live.
Open or reopen OpenClaw.app after setup, then verify the connection using the macOS remote access checks.
Troubleshooting
| Config entry | What it does |
|---|---|
LocalForward 18789 127.0.0.1:18789 |
Forwards local port 18789 to remote port 18789 |
ssh -N |
SSH without executing remote commands (port forwarding only) |
KeepAlive |
Restarts the tunnel automatically if it crashes |
RunAtLoad |
Starts the tunnel when the LaunchAgent loads at login |
Related
- Tailscale
- Authentication
- Trusted proxy auth — authenticating remote access through a reverse proxy
- Network — the hub for how OpenClaw connects, pairs, and secures devices across localhost, LAN, and tailnet