← ClaudeAtlas

tailscale-serve-patternslisted

Use when deciding how to expose a locally-running server over a tailnet (loopback bind + `tailscale serve` vs. binding directly to the tailnet interface), when a service is reachable at its Tailscale address but a request seems to bypass expected auth, when working with Tailscale identity headers (`Tailscale-User-Login` and friends) for browser auth, or when a "bind is loopback-only" security check doesn't seem to reflect what's actually reachable (Docker port publish, reverse proxy, container networking).
technicalpickles/pickled-claude-plugins · ★ 10 · AI & Automation · score 72
Install: claude install-skill technicalpickles/pickled-claude-plugins
# Tailscale Serve Patterns ## Overview Two ways to make a local service reachable over a tailnet, with very different security properties: bind loopback-only and let `tailscale serve` proxy HTTPS in front of it, or bind the process directly to the tailnet interface address. For CLI mechanics (commands, Services vs. node-serve, troubleshooting `serve status`), see the `tailscale-cli` skill instead — this skill is about which exposure shape to choose and what it implies for auth. ## The two binding patterns | | Loopback bind + `tailscale serve` | Direct bind to tailnet interface | |---|---|---| | What's exposed | `127.0.0.1:<port>` only; `serve` proxies HTTPS at `<host>.<tailnet>.ts.net` | Plain HTTP directly at the tailnet IP (e.g. `100.x.y.z:<port>`) | | Auth | Tailscale itself is the gate — only tailnet members can reach the hostname at all; TLS terminated by `tailscaled` | None unless the app adds its own — anyone on the tailnet who knows the IP:port can hit it over unencrypted HTTP | | Setup cost | One extra `tailscale serve` mapping to create/tear down | Zero extra config — just bind a different address | | Failure mode if misconfigured | Hitting `<tailnet-hostname>:<port>` directly (skipping the proxy) fails, because the app only bound loopback — this is *expected*, not a bug | A stray bind flag or default (`0.0.0.0` instead of `127.0.0.1`) silently widens exposure to the whole tailnet with no signal | **Default to loopback + `serve`.** It's the pattern used across