Both commands publish a local service through a Tailscale HTTPS name, and from the machine that runs them they look almost identical. The difference is who can reach the URL afterwards, and that difference decides which controls your application still needs. Serve keeps the audience inside the tailnet; Funnel moves it to the public internet. Pick the audience first, then pick the command that matches it.

Serve answers a question inside your tailnet

Serve proxies a local port or serves local content to devices on your tailnet, and the tailnet's access rules still apply to every request. A dashboard on port 3000 becomes reachable at an HTTPS name that only your devices can resolve and connect to. That is the right default for internal tools, because the audience is already authenticated by the tailnet and you are not adding a public surface. The URL looks like a normal website, which is exactly why people mistake it for one.

Serve can also attach identity headers describing the tailnet user to requests it forwards. If your application trusts those headers as authentication, bind the backend to localhost so nothing else can reach it directly; a backend listening on a routable address can receive requests that never passed through the proxy.

Funnel changes the audience to the open internet

Funnel publishes a selected service to anyone with the URL, and visitors do not need the client, an account or membership in your tailnet. The resource therefore needs the authentication, rate limiting and patching you would apply to any public service. Public requests do not carry Serve's tailnet identity headers, so an application that relied on them has no user identity at all under Funnel. Choosing a different publishing command does not answer the authentication question for you.

Funnel also has constraints that a tutorial from an older release may not mention. It terminates TLS with a certificate Tailscale obtains for the name, and it supports only specific listening ports, as the Funnel documentation describes. If your service expects a protocol other than HTTPS on one of those ports, Funnel is the wrong tool rather than a configuration puzzle.

Read the mode from the client, not from the port

Check the current state before you share anything, because the two modes can be configured for the same local port at different times. The status commands report what is actually published and to whom. Do not infer the audience from the fact that a URL responds.

tailscale serve status
tailscale funnel status
tailscale status --json | head -40

Command syntax has changed across client versions, so a copied invocation may select a different mode than the one you intended. Prefer the documented form for the version you run, and read the confirmation the client prints instead of assuming it. If both a Serve and a Funnel configuration exist, the public one is the one that matters for exposure. Reset the configuration you did not mean rather than layering another one on top.

Stop and re-read the status if the output does not name the audience. A published URL without a stated audience is a guess, and guesses are how internal dashboards end up on the public internet.

Test with the audience you actually meant

Test from outside the tailnet, not from the machine that published the service. A request from a tailnet device proves the proxy works; it does not prove that a stranger is excluded. Use a network that has no Tailscale client and no route into the tailnet.

curl -sI https://host.tailnet-name.ts.net
curl -sI https://host.tailnet-name.ts.net --resolve host.tailnet-name.ts.net:443:203.0.113.10

For an internal dashboard, the outside request should fail to connect or fail authorization, and the inside request should succeed with the identity you expect. For a public service, the outside request should succeed and the application should ask for its own credentials. Record which of those two outcomes you observed, because the URL alone does not tell you. If the result is the opposite of the intent, remove the public configuration first and investigate afterwards.

Publishing a service is also not the same decision as choosing an exit node, which controls where your outgoing traffic leaves. Serve and Funnel answer who may reach something you run; an exit node answers where your own connections appear to come from. Keep the two questions apart and the comparison stays simple.

Artiglyn mynjee