This guide is for teams that want OpenClaw across multi-region vpshalo nodes without ever placing a routable HTTP listener on the public internet. We use frp STCP to chain regional Mac builders to a single bastion, keep the OpenClaw gateway and doctor bound to 127.0.0.1, verify a shared token on every connect, and merge both lanes into one regional health record. It is the SSH-forward article’s cousin—same low-exposure posture, different transport.

Most OpenClaw fleets on vpshalo end up with one of three perimeters: a Tailscale MagicDNS overlay, a hand-rolled SSH reverse tunnel from loopback, or—covered here—an frp STCP chain. STCP is attractive when overlay meshes are forbidden and GatewayPorts on bastions has scared the security team: visitors must present a secret key before any TCP byte reaches your Mac, and both ends bind to 127.0.0.1. Pair the layout with the GeoDNS and SSH latency matrix for PoP picks, the Cloudflare Tunnel write-up when public TLS belongs at the edge, and the JumpHost & merged probes runbook for upstream check shapes. Public CTAs land on Home, Pricing, Help, and Purchase—no account wall.

Three operational pains shape the design. 1) “Demo” bindings on 0.0.0.0 are how perimeters die; STCP makes the easy path also the loopback path. 2) Token sprawl—each region copying secrets into systemd—becomes one auth.token for frps plus one secretKey per STCP proxy, both rotatable on a calendar. 3) Probe noise—gateway green while doctor stalls, or vice versa—merges into a single regional record so on-call sees “JP red,” not two pages for the same flap.

Concern Mechanism Acceptance signal
Hide gateway port from the internet frp STCP visitor + frps token ss -ltn shows OpenClaw only on 127.0.0.1 on builder and visitor
Authenticate before any TCP reaches OpenClaw frps auth.token + STCP secretKey Reject counters spike on key drift; OpenClaw logs unchanged
Per-region isolation One STCP proxy_name per PoP (e.g. openclaw-sin-gw) Tokyo visitor cannot dial Singapore by typo—name mismatch fails
Health rollup across gateway + doctor Merge worker joins both lanes per region One alert per regional incident, not per endpoint

Roles, ports, and the gateway/doctor chain

frps runs on a small Linux bastion you trust. It binds bindPort = 7000, enables auth.method = "token" with a long random auth.token, and optionally exposes a dashboard on 7500 behind HTTPS and basic auth. Firewall 7000/tcp to your operator subnets and your builder egress IPs—never the open internet if you can avoid it. frpc on each vpshalo Mac builder is outbound only to bastion 7000 and registers two STCP proxies per region: openclaw-{region}-gw mapping 127.0.0.1:18080 (the OpenClaw gateway) and openclaw-{region}-doctor mapping 127.0.0.1:18081 (the doctor health endpoint). Each proxy carries its own secretKey.

frpc visitors live on the operator workstation or on the merge worker. They are also outbound only and bind back to 127.0.0.1—never a LAN address. By convention we map 28080 for gateway and 28081 for doctor, so a single curl http://127.0.0.1:28080/healthz exercises the whole chain: visitor → frps token check → STCP secret check → builder loopback → OpenClaw. The gateway serves model traffic; the doctor answers a richer health JSON (queue depth, model version, GPU/Metal context). Chaining both means a stuck doctor (slow tokenizer reload) is visible even when gateway 200s look fine.

Minimal reproducible steps

Run these once per region; substitute your inventory tags for {region} (e.g. sin, tyo, hkg, icn, lax).

  • Step 1 — Pin OpenClaw to loopback. Set OPENCLAW_HOME=/var/lib/openclaw/{region} (or the macOS equivalent under /usr/local/var) and start with --gateway-bind 127.0.0.1:18080 --doctor-bind 127.0.0.1:18081. Verify curl http://127.0.0.1:18080/healthz and curl http://127.0.0.1:18081/doctor only from localhost.
  • Step 2 — Stand up frps on the bastion. frps -c frps.toml with bindPort = 7000, auth.method = "token", and a long random auth.token. Restrict 7000/tcp to operator and builder subnets at the SG/UFW/nft layer.
  • Step 3 — Wire each builder’s frpc. Two STCP proxies, distinct secretKey values, no remotePort—STCP refuses to expose a public port by design. Run frpc as a systemd or launchd unit with restart-on-failure and exponential backoff so flaps do not tempt operators to relax firewall rules.
  • Step 4 — Wire the visitor side. Two STCP visitors on the operator workstation or merge worker, both binding 127.0.0.1 (28080 gateway, 28081 doctor). The secretKey on the visitor must match the proxy. Verify with a localhost curl before granting any teammate access.
  • Step 5 — Chain gateway and doctor in one supervisor. A small worker calls 127.0.0.1:28080/healthz then 127.0.0.1:28081/doctor, joins both into {region, gw_ms, doctor_ms, status, error_class}, and posts to your TSDB. Page only when both lanes fail in the same five-minute bucket; escalate to multi-region only when two regions degrade together.
# frpc.toml — builder side, two STCP proxies on loopback
serverAddr = "bastion-sin.vpshalo.example"
serverPort = 7000
auth.method = "token"
auth.token  = "<long-random-frps-token>"

[[proxies]]
name = "openclaw-sin-gw"
type = "stcp"
secretKey = "<sk-sin-gw>"
localIP   = "127.0.0.1"
localPort = 18080

[[proxies]]
name = "openclaw-sin-doctor"
type = "stcp"
secretKey = "<sk-sin-doctor>"
localIP   = "127.0.0.1"
localPort = 18081

# frpc.toml — visitor side, both lanes bind only to 127.0.0.1
[[visitors]]
name       = "v-openclaw-sin-gw"
type       = "stcp"
serverName = "openclaw-sin-gw"
secretKey  = "<sk-sin-gw>"
bindAddr   = "127.0.0.1"
bindPort   = 28080

[[visitors]]
name       = "v-openclaw-sin-doctor"
type       = "stcp"
serverName = "openclaw-sin-doctor"
secretKey  = "<sk-sin-doctor>"
bindAddr   = "127.0.0.1"
bindPort   = 28081
If a teammate needs browser access without VPN, terminate TLS on a sibling reverse proxy in front of the visitor port—do not promote the STCP visitor to 0.0.0.0. The whole point is that loopback stays loopback.

Three things worth quoting in your runbook

  • Two scalars per region rotate: one frps auth.token shared by all builders in a region, plus one STCP secretKey per proxy. That is far less surface than a forest of ssh_config entries.
  • Brute-force scans never reach OpenClaw. STCP requires the visitor secret to match before any TCP segment is forwarded; failed connects increment frps reject counters, not OpenClaw access logs.
  • Loopback on both ends means the auditor’s preferred posture (“all traffic is on localhost”) is also your default—exceptions are rare, documented, and reviewed.

Troubleshooting playbook

Visitor cannot connect. Confirm bastion 7000/tcp is reachable from the visitor IP and that the operator subnet is allow-listed. frps logs will show auth failed on token mismatch and visitor not allowed on STCP secret mismatch—rotate one variable at a time. Gateway 200 but doctor stalls. Almost always a runaway model thread on the builder; the doctor lane is doing its job. Restart the OpenClaw worker, do not touch frp. STCP works but latency is high. When NAT traversal fails, STCP relays through frps; pin builders to a vpshalo PoP close to the bastion (use the SCP/SFTP RTT matrix for picks). Token drift after rotation. Visitor still uses the old secretKey; rotate proxy and visitor in the same maintenance window and confirm with curl 127.0.0.1:28080/healthz before silencing alerts. Probes flap together. If gateway and doctor both fail at the exact same second, it is the tunnel, not OpenClaw—check frps process supervision and ServerAliveInterval-style keepalives on frpc.

Disclaimer: frp flag and option names evolve across major versions; verify auth.method, STCP option spellings, and visitor bindAddr against the build you deploy. OpenClaw releases differ in --gateway-bind and --doctor-bind spellings—this article illustrates intent, not a vendor SLA. The pattern complements rather than replaces SSH or Tailscale guides linked above.

When the steps above are scripted, the perimeter becomes boring on purpose: loopback inside each vpshalo region, an authenticated frp STCP tunnel, no public listener, calendar-driven token rotation, and a single merged alert per region instead of an inbox of probe noise. Provision remote Mac builders in the PoPs you actually measured; capacity, registry mirrors, and frp visitors should move together.

Public next steps

Place builders next to your bastion

Open Home, compare Pricing, read Help, browse the Blog, or jump to Purchase. Lower RTT between frps and frpc stabilises STCP relays and probe windows together—no login wall on public pricing or help pages.

Region purchase pages: Tokyo · Seoul · Hong Kong · Singapore · US West

Rent a Mac now View plans Help center More articles