gateway.bind to lan without OPENCLAW_GATEWAY_TOKEN in a path launchd reads produces refusing to bind without auth, while SSH-only engineers wonder why Control UI works in Terminal but not from a colleague laptop. This article maps loopback versus LAN decision trees, freezes a single source of truth for .env and openclaw.json, walks six acceptance steps with a command block, explains gateway.reload.mode=hybrid versus hard restart boundaries plus gateway.controlUi.allowedOrigins, and closes with LaunchAgent drift checks and cloud Mac placement. Read alongside launchd token fix, upgrade remote auth, diagnostic ladder, and official install-daemon so bind changes do not masquerade as model failures.
Loopback versus lan: when non-loopback bind is worth the auth tax
OpenClaw Gateway defaults to loopback because the threat model on a rented cloud Mac is not your kitchen Wi-Fi: the host sits on a provider network with other tenants, automated scanners, and engineers who SSH from three continents. Loopback plus ssh -L or Tailscale keeps port 18789 off unintended interfaces while still letting you open Control UI in a local browser. That pattern remains the right default for a single engineer who never shares dashboard access and runs all channel installs from the same SSH session documented in the headless SSH runbook.
gateway.bind=lan enters when something on the LAN segment must dial Gateway directly: a second Mac on the same office subnet running the OpenClaw macOS app in remote mode, an internal health aggregator that cannot maintain SSH tunnels per host, or a Control UI session from a teammate machine without forwarding 18789 through one laptop. Recent builds refuse to listen on non-loopback addresses unless gateway.auth.mode and a valid token are configured—there is no silent fallback to anonymous LAN access. Treat that refusal as a feature: it stops the classic Friday change where someone widens bind for debugging and forgets to narrow it before the weekend.
Solo SSH tunnel: stay on loopback; token optional for local probes; document the forward command on the change ticket.
Small team, zero-trust edge: prefer Tailscale serve or ACL-scoped paths before raw LAN bind on provider RFC1918.
Fixed internal probes: lan plus token when monitoring must hit /healthz without tunnel maintenance.
Multi-device Control UI: lan plus token plus allowedOrigins; never expose 18789 on a public interface without edge ACLs.
Production channels: WeChat ClawBot and Telegram still depend on Gateway uptime; bind mode does not replace launchd persistence from 24/7 setup.
| Pattern | Bind | Auth | Typical mistake |
|---|---|---|---|
| One engineer, SSH only | loopback | optional locally | Opening provider security groups “just to test” |
| Remote macOS app | lan or tailnet | token required for lan | Token in shell rc but not in plist env |
| Internal SRE probe | lan on private NIC | token in Authorization header | Probe URL still targets 127.0.0.1 |
| Post-upgrade drift | config says lan | deprecated gateway.token key | Doctor clean, RPC unauthorized |
Platform owners should record bind intent in the asset inventory: production lan, staging loopback-only, or tunnel-only. That single field prevents on-call from guessing whether unauthorized responses mean a wrong token or a probe aimed at the wrong address. If you have not finished first-boot install-daemon acceptance, complete official install-daemon before widening bind—the wizard and doctor both assume a stable loopback baseline.
Single source of truth: .env OPENCLAW_GATEWAY_TOKEN and openclaw.json auth mode
The most durable production pattern on KVMNODE cloud Macs separates secrets from structure. Place the gateway token in ~/.openclaw/.env as OPENCLAW_GATEWAY_TOKEN, restrict file mode to the service user, and keep openclaw.json declarative: gateway.bind, gateway.auth.mode (typically token), port, and reload settings—without duplicating the secret string in JSON where it lands in backups, screenshots, and paste-heavy Slack threads. OpenClaw resolves env vars at process start; launchd must load the same file the interactive CLI uses or you recreate split brain from the upgrade auth runbook.
LaunchAgent drift often starts when someone exports a token in .zshrc, verifies Gateway in SSH, then wonders why the plist-owned daemon returns unauthorized. Fix by mirroring env into the plist EnvironmentVariables block or by pointing both CLI and daemon at the documented state directory. Never commit .env to git; rotate tokens when engineers leave shared hosts. The deprecated gateway.token key must not linger—new schema reads gateway.auth.token or env substitution, and silent ignores produce dashboard loops documented on L2 of the diagnostic ladder.
| Location | Store | Avoid |
|---|---|---|
| ~/.openclaw/.env | OPENCLAW_GATEWAY_TOKEN | World-readable permissions |
| openclaw.json | gateway.auth.mode, gateway.bind | Plaintext token duplicate |
| LaunchAgent plist | PATH, WorkingDirectory, env file path | Stale token copied once in 2025 |
| Chat / tickets | Redacted last four chars only | Full token in screenshots |
One token, one env file, one plist profile—three copies of the same secret is not redundancy, it is drift.
When rotating, generate the new token, update .env, run openclaw gateway restart, then update clients and probes in the same change window. Partial rotation leaves mobile apps authorized while cron probes fail, which looks like flaky infrastructure instead of auth hygiene. Pair rotation checks with openclaw config get gateway.auth.mode and the launchd token article so missing env vars do not reopen after every minor OpenClaw upgrade.
Six-step walkthrough: stop stale listeners, set lan bind, install daemon, healthz, client token
Execute on the same cloud Mac that owns the LaunchAgent Gateway, typically over SSH on KVMNODE. Do not edit bind on a laptop while Gateway lives in Singapore—that splits config truth and breaks remote client acceptance.
Stop stale processes: openclaw gateway stop, confirm lsof -nP -iTCP:18789 is empty per install troubleshoot checklist.
Write .env: add OPENCLAW_GATEWAY_TOKEN with a fresh random value; chmod 600 the file.
Set config: gateway.bind=lan, gateway.auth.mode=token; avoid plaintext token in JSON.
Refresh supervisor: openclaw gateway install or install --force when doctor requests; verify plist PATH matches which openclaw.
Health probe: curl -sf http://127.0.0.1:18789/healthz then repeat from LAN IP with Authorization: Bearer token header.
Client acceptance: connect Control UI or remote app with token; record bind mode, token rotation date, and first successful deep status JSON on the ticket.
export PATH="/opt/homebrew/bin:/usr/local/bin:$PATH" openclaw gateway stop lsof -nP -iTCP:18789 -sTCP:LISTEN || true umask 077 printf 'OPENCLAW_GATEWAY_TOKEN=%s\n' "$(openssl rand -hex 32)" >> ~/.openclaw/.env openclaw config set gateway.bind lan openclaw config set gateway.auth.mode token openclaw gateway install --force openclaw gateway restart openclaw gateway status --deep curl -sf "http://127.0.0.1:18789/healthz" curl -sf -H "Authorization: Bearer $(grep OPENCLAW_GATEWAY_TOKEN ~/.openclaw/.env | cut -d= -f2)" \ "http://$(ipconfig getifaddr en0):18789/healthz"
Tip: Replace en0 with the NIC your probe uses; on some cloud images the primary interface differs—use ifconfig once and freeze the name in the runbook.
Distributed teams should wrap acceptance in explicit remote execution so every engineer hits the leased host, not a local clone of config. During acceptance week, schedule daily healthz checks from both loopback and LAN perspectives even when no human opens Control UI—silent bind regressions after plist edits show up as probe failures before channel users notice.
Hybrid reload versus hard restart, plus controlUi allowedOrigins for remote browsers
gateway.reload.mode=hybrid lets OpenClaw apply some configuration changes without a full process restart—useful when tuning channel routing or soft limits during business hours. Not every field is hot-safe. Bind address, listen port, auth mode, and token source changes require a supervised restart because the kernel socket and auth middleware initialize at boot. Teams that rely on hybrid alone after switching to lan often see half-applied state: status JSON still mentions loopback while lsof shows a LAN listener from an older binary.
| Change | Hybrid reload | Requires restart |
|---|---|---|
| Channel routing tables | Often yes | When probe shows stale routes |
| gateway.bind / port | No | Always restart plus lsof verify |
| gateway.auth.mode | No | Always restart |
| OPENCLAW_GATEWAY_TOKEN rotation | No | Restart then update clients |
| controlUi.allowedOrigins | Sometimes | Restart if browser still blocked by CORS |
Remote Control UI sessions need explicit browser origin allowlisting. Set gateway.controlUi.allowedOrigins to the HTTPS origins your team uses—internal dash hostnames, not wildcard public internet. Pair origins with token auth so a random website cannot drive your Gateway even when bind is lan on a private subnet. After edits, prefer openclaw gateway restart over assuming hybrid picked up CORS headers; browser errors masquerade as “Gateway down” in chat.
Document the reload boundary on the same page as cron probes from the channels probe runbook: soft changes log internally, hard changes page on-call. That split keeps Friday tuning from turning into Sunday incident calls when someone rotated a token without restart.
Triage fork, LaunchAgent drift checks, and cloud Mac placement for lan Gateway
refusing to bind without auth: bind left loopback but token missing from env plist reads—fix .env and auth.mode before blaming ports.EADDRINUSE on 18789: stale manual gateway start plus plist daemon—clear PIDs per install checklist.unauthorized on healthz: probe omitted Bearer token or uses rotated secret CLI still caches.Runtime running, probe failed: listen address and probe URL disagree—common after lan flip; align target IP with gateway status --deep from the diagnostic ladder L1 row.
LaunchAgent drift: compare which openclaw in SSH versus plist ProgramArguments; mismatch triggers split brain per CLI align.
Env drift: launchctl print environment block must include token path; see launchd token fix.
Post-change evidence: attach deep status JSON, curl healthz codes, and plist label to every bind ticket.
| Host | lan Gateway experience | Ops conclusion |
|---|---|---|
| Closed laptop | Sleep drops LAN listener; probes flap | Experiments only |
| Shared office VM | Noisy neighbor; non-Apple launchd norms | Self-built monitoring burden |
| KVMNODE dedicated cloud Mac | Stable NIC, launchd daemon, six regions | Default for production lan bind plus channels |
Note: Widening bind without token on a cloud Mac invites scanner noise and false model-timeout pages—complete auth before any provider security-group hole.
When the same host runs iOS CI and OpenClaw, memory spikes can slow healthz without dropping bind—upgrade toward M4 Pro or split pools instead of reverting to loopback mid-incident. Personal Macs add sleep and PATH splits; shared VMs lack contractible Apple Silicon acceptance. For teams that need auditable dedicated hosts where lan Gateway, channels, and optional CI share one machine, KVMNODE Mac Mini cloud rental is usually the better fit: dedicated Apple Silicon, six regions, day-through-month terms, and the same runbook language for 18789, launchd, and tier upgrades. SKUs on pricing, procedures in the Help Center, order via order page.