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.
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.
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 fails closed (no remote fallback masking). 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