Is a Tailscale address a safe way to reach an OpenClaw gateway from your phone, or should it stay on localhost?
Julian Park
October 7, 2026
Keep the OpenClaw Gateway on localhost, and use Tailscale Serve if the phone needs to reach it. Serve is tailnet-only. The Gateway process stays on 127.0.0.1, and Tailscale fronts it with HTTPS for devices on that tailnet, including a phone running the Tailscale app. A raw Tailscale IP with the Gateway bound to the tailnet interface also works, and it is the weaker of the two: no HTTPS, a shared token, and plaintext ws://. Tailscale Funnel is a third, different thing. Funnel publishes an HTTPS URL on the public internet and OpenClaw will not start it unless you set a password.
Localhost alone is the right bind and the wrong destination for a phone. The phone is not the machine running the Gateway. 127.0.0.1:18789 on the server is the server talking to itself. Your phone cannot open that address and land on the closet computer. The safe pattern in the OpenClaw docs is loopback plus Serve, not “turn loopback off because I bought a tailnet.”
What does Serve actually expose?
OpenClaw can own the Tailscale route for you. gateway.tailscale.mode has three values. off is the default: OpenClaw does not manage Serve or Funnel. That does not log the machine out of Tailscale. It only means this Gateway is not advertising itself through Tailscale. serve is tailnet-only Serve, Gateway still on 127.0.0.1. funnel is public HTTPS and refuses to start unless auth mode is password.
The serve config the docs show is small on purpose:
{
gateway: {
bind: "loopback",
tailscale: { mode: "serve" },
},
}
You can also start it from the CLI with openclaw gateway --tailscale serve. The dashboard URL is an HTTPS name on your tailnet, not the loopback port printed on the server. MagicDNS and HTTPS certificates have to be on for the tailnet. The tailscale CLI has to be installed and logged in on the Gateway host. If HTTPS is missing, the CLI prompts. Serve and Funnel both require gateway.bind to stay loopback.
That last rule is the one I would write on a card. The Tailscale address is a front door. The process is still only listening locally, on a dedicated loopback port that managed ingress proxies to, while ordinary local clients keep the normal Gateway port. Startup fails closed rather than sharing listener provenance between those paths. When the Gateway stops, OpenClaw releases the foreground Tailscale claim. You are not left with a public or tailnet route pointing at a dead process that some later program inherits by accident. If an older install left an orphaned claim holding the HTTPS port, the docs tell you to inspect tailscale serve status --json and confirm the process before you stop it. Do not clear a route you cannot identify.

Who is allowed in once Serve is on?
Tailnet-only is not the same sentence as “only my phone.” Everyone on the tailnet who can open that HTTPS name can attempt the connection. Tailscale ACLs still apply. If you share a tailnet with relatives, a work account, or an exit node you added for someone else, those identities are on the same private network as the Gateway’s Serve URL. OpenClaw does not invent a second guest list inside Tailscale. Your tailnet is the guest list.
On top of that, Serve can satisfy Control UI and WebSocket auth with Tailscale identity headers when gateway.auth.allowTailscale is true. OpenClaw does not trust the header because it showed up. It checks that the request hit the dedicated managed-Tailscale listener with Tailscale’s forwarded-for, forwarded-proto, and forwarded-host headers, then resolves the forwarded client address with tailscale whois on the local daemon and matches it to the tailscale-user-login header. The same headers on the ordinary Gateway listener do not count. Direct loopback, a LAN address, a raw tailnet IP, and a custom reverse proxy do not get this tokenless path.
That bypass is narrow, and the docs list the edges.
- It covers Control UI WebSocket auth, and read-only GET and HEAD for Control UI profile avatars.
- It does not cover other HTTP API routes, including
/v1/,/tools/invoke, and/api/channels/. Those follow normal Gateway auth. - It does not skip device identity. Clients without a device identity are still rejected. Node-role connections still go through pairing and the normal auth checks.
- For a Control UI operator session that already has browser device identity, a verified Tailscale identity can skip the bootstrap-token or QR pairing round trip.
The tokenless flow assumes the Gateway host itself is trusted. If untrusted code might run on that same machine, set gateway.auth.allowTailscale to false and require a token or password. I would do that on any box that also runs other people’s containers. A tailnet login is not a substitute for “this host is mine.”
allowTailscale defaults to true when mode is serve, in the gateway config reference. The stable-URL page adds a finer cut: for Serve with token auth, OpenClaw enables the identity-header behavior unless you set it false. Password mode and trusted-proxy mode keep their own auth boundary unless you opt in. Serve cannot be combined with gateway.auth.mode: "none". Local onboarding generates a token by default even on loopback. You do not get a silent open Gateway just because you enabled Serve.
Is a tailnet IP bind the same thing?
No. gateway.bind: "tailnet" listens directly on the tailnet address. There is no Serve, no Funnel, and no HTTPS from OpenClaw’s Tailscale integration. The docs show token auth in that example, and the client URL is ws:// plus the tailnet host and port 18789. Plain HTTP is called a downgraded transport: device identity signing does not encrypt the page, the shared secret, or the Gateway traffic. Prefer Serve when the thing in your hand is a phone browser or the Control UI.
When a tailnet IPv4 exists, that bind also keeps http://127.0.0.1:18789 for same-host clients. If Tailscale is not up yet at startup, it falls back to loopback only, and you restart after Tailscale connects if you want the direct tailnet listener. Neither path adds LAN or public exposure by itself. gateway.bind: "auto" is a different switch: it uses 0.0.0.0 inside a detected container and prefers loopback otherwise. Do not use auto as a synonym for tailnet.
Non-loopback binds, including an explicit tailnet bind, require Gateway auth: a token, a password, or trusted-proxy mode. The remote-access page also says plaintext ws:// is accepted for .ts.net names, private addresses, and similar non-public hosts. Public hosts must use wss://. Accepted is not the same as recommended. I use the tailnet bind for a CLI on another machine I already trust, and Serve for anything with a screen I might open on a phone.
What about Funnel, since it also produces a Tailscale URL?
Funnel is the option to refuse unless you mean to publish the Control UI. It is public HTTPS through tailscale funnel. OpenClaw requires gateway.auth.mode: "password" and will not start Funnel without it, specifically to avoid public exposure. The password should come from OPENCLAW_GATEWAY_PASSWORD or a secret reference, not a password committed in openclaw.json. The CLI form is openclaw gateway --tailscale funnel --auth password.
A Funnel URL still works from inside the tailnet. Tailscale marks public requests as Funnel traffic and sends tailnet peers through the Serve identity path instead. OpenClaw recognizes both on its dedicated listener and still requires the Funnel password. Identity headers do not replace that password. allowTailscale is a Serve behavior, not a way to make Funnel tokenless.
Funnel has its own prerequisites: Tailscale v1.38.3 or newer, MagicDNS, HTTPS, a funnel node attribute, and only ports 443, 8443, and 10000 over TLS. On macOS it needs the open-source Tailscale app variant. Portals, the extra private ingress OpenClaw uses for application previews, never inherit Funnel. They stay on separate private Serve HTTPS ports, and your tailnet ACLs have to allow those ports as well as the Gateway port. The docs also say to avoid Funnel for browser control. If the Gateway on one machine drives a browser on another, run a node host on the browser machine, keep both on the tailnet, and treat that pairing like operator access.

I would not put Funnel on a personal Gateway to save myself installing Tailscale on the phone. Installing the Tailscale app is the smaller change. Funnel means anyone who learns the name can hit the login. A shared password on a public URL is a single string standing between the internet and a process that can run tools on your host. Serve keeps that problem inside a network you already administer.
What if Tailscale Serve is already managed somewhere else?
You can point a native tailscale serve or Funnel route at the ordinary Gateway listener when another service owns the route. That path is not managed OpenClaw ingress. You set gateway.trustedProxies to the immediate source, narrowly, and that proxy has to overwrite or safely rebuild X-Forwarded-For. OpenClaw then treats the request as generic trusted-proxy ingress and applies normal Gateway auth. It does not call tailscale whois. allowTailscale cannot make this path tokenless.
Without an explicitly trusted source and a valid non-loopback forwarded client, authenticated routes fail with proxy_attribution_required. Adding 127.0.0.1 to trustedProxies means you trust other processes on that host to supply the client address. The docs say to keep token or password auth on unless every process on the host is inside the same trust boundary. Public Funnel that you arranged yourself is rejected when auth mode is none.
Older installs sometimes left an HTTPS route aimed at a LAN bind. openclaw doctor will inspect that and leave it alone, because it cannot prove who owns the route. If you confirm it is a stale OpenClaw route, the Tailscale page tells you how to remove only that root handler and then set loopback plus gateway.tailscale.mode: "serve" yourself. Named Tailscale Services are not supported by managed ingress. If a serviceName key is still in config, openclaw doctor --fix disables managed ingress and removes the key. You then clear the retained Service route in Tailscale before turning device Serve back on.
How does the phone fit, concretely?
Put the phone on the same tailnet as the Gateway host. Log the host’s Tailscale in as a machine you mean to keep up. Enable Serve. Open the HTTPS tailnet name from the phone, or point the iOS or Android app at that Gateway. Nodes use the same WebSocket as the Control UI. The phone app is a client. Serve does not move the agent onto the phone, and it does not keep the agent running if the host sleeps. It only gives an already-running Gateway a private HTTPS name.
SSH is the other documented way in, and it is the right fallback when you do not want Tailscale managing a URL. A tunnel to 127.0.0.1:18789 makes the phone or laptop look local. It is more annoying every time the tunnel drops, and it does not depend on Serve identity headers. I use SSH when I am repairing a box and Serve when I am actually chatting. Both keep the bind on loopback. Neither is a reason to set the bind to LAN so the phone can “just see it” on the house Wi-Fi. House Wi-Fi is a worse guest list than a tailnet.
Startup order bites people who reboot the closet computer. If the Gateway starts before the Tailscale daemon is connected, managed Serve waits up to 90 seconds while status is NoState or Starting. Any other daemon state fails closed immediately. If hostname discovery fails, the Gateway can keep an active claim and still leave managed portals unavailable until you fix tailscale status and restart. Check that on the host, not from a screenshot of the phone.
Which setup should you actually run?
For a phone and a Gateway you trust on hardware you trust: bind: "loopback", tailscale.mode: "serve", token auth left in place, and allowTailscale left on only if you are comfortable that a verified tailnet identity may satisfy the Control UI WebSocket. Tighten it to false if the host is shared. Keep Funnel off. Use a direct tailnet bind only when you want a CLI on another tailnet device and you accept plaintext.
Stay on localhost in the sense that matters: the process listens there. Use a Tailscale address in the sense that matters for the phone: Serve’s HTTPS name, tailnet-only, on a tailnet whose member list you would say out loud. The address is safe when those are the same design. It is not safe because the hostname contains ts.net.