Set Up a Stable Tailnet HTTPS URL for Your Gateway
This page explains how to give your Gateway a stable, tailnet-only HTTPS URL using Tailscale Serve, keeping it hidden from the public internet. It covers prerequisites and the outcome for secure, private access.
Read this when
- Replacing per-client SSH tunnels with one private Gateway URL
- Connecting macOS, iOS, or Android clients to a remote Gateway
- Diagnosing a Tailscale Serve URL that works locally but times out remotely
Tailscale Serve assigns your Gateway a single HTTPS address while keeping the Gateway port hidden from both your LAN and the wider internet. The Gateway continues to listen only on loopback, and Tailscale terminates HTTPS with a valid certificate before forwarding traffic to it.
The outcome is https://<host>.<tailnet>.ts.net, which devices permitted on your tailnet can reach, while the public internet cannot. The corresponding WebSocket address is wss://<host>.<tailnet>.ts.net.
For a publicly accessible URL, choose Tailscale Funnel instead. Funnel is exposed to the public, and OpenClaw mandates password authentication for it.
Before you begin
These are the prerequisites:
- MagicDNS must be turned on for your tailnet.
- HTTPS certificates must be enabled in the Tailscale admin console, located under DNS > HTTPS Certificates.
- Tailscale needs to be installed and authenticated on the Gateway host.
- The Gateway must already be set up with token, password, or trusted-proxy authentication. Serve is incompatible with
gateway.auth.mode: "none".
OpenClaw finds the Tailscale CLI on its own. It looks in tailscale on PATH, the macOS app bundle at /Applications/Tailscale.app/Contents/MacOS/Tailscale, other matching app installations under /Applications, and the system locate database. Adding the macOS app-bundle binary to PATH is unnecessary.
1. Enable Serve while keeping loopback bind
Execute these commands on the Gateway host:
openclaw config set gateway.bind loopback
openclaw config set gateway.tailscale.mode serve
openclaw gateway restart
The matching configuration looks like this:
{
gateway: {
bind: "loopback",
tailscale: {
mode: "serve",
},
},
}
OpenClaw sets up Tailscale to serve HTTPS on port 443 and forward to a private ephemeral loopback listener that the Gateway owns. The standard Gateway listener stays on 127.0.0.1:18789 by default so local clients can connect directly.
Optional identity-header auth
To explicitly permit Tailscale identity headers for Control UI WebSocket authentication:
openclaw config set gateway.auth.allowTailscale true
When using Serve with token auth, OpenClaw turns this behavior on by default unless you assign it false. Password and trusted-proxy modes preserve their explicit authentication boundary unless you choose to enable it.
This option lets a verified Tailscale identity pass the Control UI WebSocket shared-secret check. OpenClaw validates the forwarded client address using tailscale whois and compares it against the tailscale-user-login header. It only takes effect when the request reaches OpenClaw through Serve on its dedicated managed-Tailscale listener with the expected forwarded headers present.
It does not authenticate HTTP API endpoints, remove browser device identity requirements, authenticate node-role connections, or bypass node pairing. The complete contract is described in Tailscale identity headers.
2. Allow HTTPS in your tailnet policy
Serve respects Tailscale access controls. If your tailnet uses a restrictive policy, grant the client devices permission to reach the Gateway host on TCP port 443.
Without that grant, the Serve URL may function on the Gateway host itself but silently time out from all other devices. This failure looks like a Gateway problem when the tailnet policy is actually the cause.
Pick the form that fits your tailnet policy file.
Modern grants policy
Insert this object into the existing grants array:
{
"src": ["autogroup:member"],
"dst": ["<gateway-host-or-ip>"],
"ip": ["tcp:443"]
}
For instance, swap <gateway-host-or-ip> for a host alias defined in your policy, like gateway-host, or use an address such as 100.x.y.z.
Older ACL policy
Insert this object into the existing acls array:
{
"action": "accept",
"src": ["autogroup:member"],
"dst": ["<gateway-host-or-ip>:443"]
}
autogroup:member permits every authenticated member of the tailnet. For a more restrictive policy, replace it with a narrower user, group, tag, or device selector that covers only the clients requiring Gateway access. Consult the Tailscale documentation on grants and ACLs.
3. Verify the route and loopback boundary
On the Gateway host, verify that Serve is running:
tailscale serve status
The output should display an HTTPS route for https://<host>.<tailnet>.ts.net forwarding to a private ephemeral loopback port owned by the Gateway.
From a different device on the same tailnet, test the HTTPS response:
curl -sS -o /dev/null -w '%{http_code}\n' https://<host>.<tailnet>.ts.net/
Expect 200 for the Control UI root. If this request times out but the identical command returns 200 on the Gateway host, first check the TCP 443 grant from the previous step.
As a final check, confirm that the Gateway process did not expose its own port to the network:
lsof -nP -iTCP:<port> -sTCP:LISTEN
For the default port, substitute <port> with 18789. The Gateway listener should be bound to 127.0.0.1:<port>, not 0.0.0.0:<port> or a LAN or tailnet address. Tailscale owns the HTTPS listener and the proxy path.
4. Use the URL from clients
macOS app
In the OpenClaw macOS app:
- Navigate to Settings > Connection.
- Change OpenClaw runs to Remote (another host).
- Change Transport to Direct (ws/wss).
- Put
wss://<host>.<tailnet>.ts.netinto Gateway URL. - Click Test remote.
The app now connects directly through Tailscale Serve, so the per-client SSH tunnel is no longer required.
iOS and Android companion apps
The iOS and Android apps connect straight to the Gateway WebSocket and do not handle an SSH-tunnel transport. Use the same wss://<host>.<tailnet>.ts.net endpoint when pairing or generating a setup code. This provides mobile clients with a secure route they can use from any location on the tailnet.
Refer to iOS app setup and Android connection setup for their pairing instructions.
Troubleshooting
The URL times out from other devices
Execute the identical curl command directly on the Gateway host. When the host replies with 200 but other tailnet devices experience timeouts, refine the tailnet policy grant to include or restrict TCP 443.
The certificate is not issued or the first request is slow
Verify that MagicDNS and HTTPS certificates are switched on within the Tailscale admin console. The initial certificate issuance may cause the first HTTPS request to be slower; allow it to complete before trying again.
The serve command is unavailable
Upgrade Tailscale and verify that the installed client version supports the current tailscale serve command. The Serve CLI underwent changes starting with Tailscale 1.52. For details, consult the Tailscale Serve command reference.
Tailscale identity headers are not accepted
Ensure that gateway.auth.allowTailscale is set to true and that the request reaches the Serve URL. Requests made via direct loopback, LAN, raw tailnet IP, or custom reverse proxies are not eligible for Tailscale identity-header authentication.