Skip to main content
Self-Hosting & Privacy

Tailscale Serve vs Funnel Explained

Tailscale Serve keeps a service on your tailnet; Funnel puts it on the public internet. Real commands, TLS setup, and the gotchas that cause 404s.

Milan BuhaNovember 12, 20266 min readTailscale VPN
ShareXin
Tailscale Serve vs Funnel Explained

Tailscale Serve vs Funnel Explained

Jellyfin is finally running on the homelab, and a family member who doesn't own a laptop with Tailscale on it wants to watch something tonight. tailscale serve won't get them in. tailscale funnel might — but which one, and what else breaks if you pick wrong?

TL;DR

  • Serve shares a local service with your tailnet only — nothing leaves your private network.
  • Funnel extends that to the public internet, for one service you deliberately choose.
  • Both need MagicDNS and HTTPS certificates enabled first, or the URL 404s even though the command "succeeded."
  • Funnel only speaks on ports 443, 8443, or 10000 — nothing else.
  • Scope Funnel to a single path, not the whole host, if the service exposes more than one thing.
3ports Tailscale Funnel will ever use (443, 8443, 10000), confirmed running both Serve and Funnel on my homelab

Serve vs Funnel in one sentence

Both commands are part of the same tailscale serve subsystem — Funnel is a flag on top of Serve, not a separate tool. Tailscale's own announcement frames Serve as "share within your tailnet" and Funnel as "share with the world."

Serve Funnel
Audience Devices on your tailnet only Anyone with the URL, on the public internet
Auth required Tailscale identity (WireGuard) None — it's a public HTTPS endpoint
TLS Automatic tailnet cert Automatic public cert
Ports Any local port you choose 443, 8443, or 10000 only
Typical use Admin dashboards, Immich, Grafana One service for someone outside your tailnet
Risk if misused Low — still gated by tailnet membership Exposes exactly what you serve, to anyone

Prerequisites both features share

Neither command works stand-alone. Per Tailscale's HTTPS setup docs and the Tailscale Funnel feature docs, you need, in order:

  1. MagicDNS enabled for the tailnet (Admin console → DNS).
  2. HTTPS Certificates enabled (Admin console → DNS → HTTPS Certificates), which publishes your tailnet's DNS name to a public ledger — acknowledge that prompt.
  3. A funnel node attribute in the tailnet policy file — the CLI adds this for you automatically the first time you run tailscale funnel, but if your policy file is locked down or managed separately, add it by hand: "nodeAttrs": [{"target": ["autogroup:member"], "attr": ["funnel"]}]. Serve doesn't need this attribute — it's Funnel-only.

Warning

Skip step 2 and tailscale funnel will report success in the terminal, then the URL 404s or TLS-errors in the browser. This exact sequence — command exits clean, URL doesn't work — is the single most common Funnel complaint on the Unraid forums. Enable HTTPS certificates before touching either command.

Tailscale Serve: keep it on the tailnet

Serve maps a local port to a path on your tailnet's HTTPS endpoint. For Immich running on port 2283:

tailscale serve https / http://localhost:2283

That's it — https://<your-device>.<tailnet-name>.ts.net/ now reaches Immich, but only from devices logged into your tailnet. No open port on your router, no reverse proxy, no cert to renew by hand.

Use Serve for anything you don't want outside your household: admin panels, Grafana, Proxmox's own web UI, a staging build. The access control is "is this device on my tailnet," which for most homelabs is already tighter than a password.

Tailscale Funnel: hand out a public link

Funnel takes the same Serve mapping and additionally opens it to the internet. Two commands, run once:

tailscale funnel 443 on
tailscale serve https / http://localhost:8096

The tailscale serve CLI reference and tailscale funnel CLI reference confirm the pattern: serve defines what gets shown, funnel flips the switch on whether it's public. Running tailscale funnel status afterward lists every currently-funneled port, which is worth checking periodically so you know exactly what's public.

That maps Jellyfin (port 8096) to a public HTTPS URL. Send the link to whoever needs it — they don't install Tailscale, don't need an account, and don't see anything else on the box.

Note

Funnel traffic reaches your app as proxied traffic, not as a tailnet peer — app-level logs won't show the visitor's real IP the way tailnet traffic does. That's expected, not a bug.

Path-scoped Funnel: don't expose the whole host

If a service exposes more than one route — a file server, a reverse-proxied set of apps on one machine — funneling the host root makes everything on that port public, not just the one thing you meant to share. Scope it to a single path instead:

tailscale serve https:443/shared/ http://localhost:8080/shared/
tailscale funnel 443 on

Only /shared/ becomes reachable from the public internet; every other path on that same local port stays tailnet-only, because Serve's path mapping is what Funnel exposes — it never exposes the raw port.

Making it stick — and undoing it

Run either command with --bg to survive a reboot or tailscale down && tailscale up:

tailscale funnel --bg 443 on

Without --bg, the mapping only lasts for the terminal session. Per the tailscale funnel CLI reference, the config is written to disk and tailscaled restores it on startup — so --bg is what you want for anything long-lived, like Jellyfin.

To undo everything and start clean:

tailscale funnel reset
tailscale serve reset

Or append off to a specific mapping to remove just that one:

tailscale funnel 443 off

Checking what's actually exposed right now

It's easy to funnel something for a one-off and forget it's still public weeks later. tailscale funnel status lists every active funnel on the current node; tailscale serve status shows the full set of Serve mappings, public or not. Run both after any homelab change — a new container, a reverse proxy swap, a docker compose up that quietly reused an old port — and confirm the list matches what you intend to be reachable from outside your tailnet. Treat an unexpected entry the same as an unexpected open port on your router: investigate before you assume it's harmless.

Decision rule: which one should you actually use

Default to Serve. It costs nothing in exposure and covers the vast majority of homelab sharing — anything you'd otherwise reach by opening a port or standing up a reverse proxy. Reach for Funnel only for the one specific service a person outside your tailnet genuinely needs, and scope it to a path rather than a whole host. If you're tempted to Funnel something "just in case someone needs it later," that's a sign it should stay on Serve until an actual outside visitor exists.

For the networking layer underneath both features, see our guide on installing Tailscale on a homelab and, if you want outbound traffic routed through home instead of split-tunneled, how Tailscale exit nodes work. The complete mesh VPN guide covers where Serve and Funnel sit in the wider Tailscale feature set.

FAQ

Is Tailscale Funnel free?

Yes — Funnel is available on Tailscale's free personal plan, same as Serve. Both are part of the core product, not a paid add-on.

Does Tailscale Funnel expose my whole device to the internet?

No. Funnel only exposes the specific port and path you map with tailscale serve — everything else on the device stays reachable only from your tailnet, exactly as before.

Why does my Tailscale Funnel URL return a 404?

Almost always because MagicDNS or HTTPS Certificates aren't enabled yet in the tailnet admin console. Enable both before running tailscale funnel, per Tailscale's HTTPS setup docs.

Can I use Tailscale Funnel without MagicDNS?

No — Funnel depends on the tailnet DNS name that MagicDNS assigns, and on the HTTPS certificate tied to that name. Both must be enabled first.

Related stories

More from Self-Hosting & Privacy

Stay in the loop

Get the latest articles delivered to your inbox. No spam, unsubscribe anytime.

Read next

Riester Rente Steuererklärung: Anlage AV

How to file Anlage AV correctly for a Riester Rente: what triggers the Günstigerprüfung, the 2025 change that moved child allowances to Anlage Kind, and what I actually entered on my own form.

Continue Reading