SSD Nodes Learn Hosting plans →
Guides Matt ConnorBy Matt Connor

Tailscale Funnel: ports, limits, bandwidth

Tailscale Funnel accepts only ports 443, 8443 and 10000. Here are the prerequisites, the bandwidth reality, and how to check yours is serving.

What Tailscale Funnel allows

Tailscale Funnel publishes one service from one machine in your tailnet to the public internet, and it will only listen on three TCP ports: 443, 8443 and 10000. There is no fourth port, and no setting adds one. Tailscale's documentation is direct about it: "Funnel can only listen on ports 443, 8443, and 10000."

Two other rules decide whether Funnel fits your plan. Every public connection is TLS (transport layer security) encrypted, because the public name is a certificate for your-machine.your-tailnet.ts.net. And every byte crosses Tailscale's Funnel relay servers, which is why the same documentation describes Funnel traffic as "subject to non-configurable bandwidth limits". Neither of those is something you can change.

The three ports Funnel will accept

The public listener is chosen by one of three flags, and each flag accepts only 443, 8443 or 10000.

tailscale funnel --bg localhost:3000
tailscale funnel --bg --https=8443 localhost:3000
tailscale funnel --bg --set-path=/hooks localhost:3000
tailscale funnel status

--https=<port> is the default mode and serves HTTP over TLS, with the daemon holding the certificate. --tcp=<port> forwards raw TCP, so your application has to present the certificate itself, because the public side of a Funnel is always TLS. --tls-terminated-tcp=<port> does the middle case: the daemon terminates TLS and hands plain TCP to your app, which is how a non-HTTP service gets a valid public certificate without knowing anything about certificates. All three take the same restricted set of numbers.

To remove one mapping, repeat the command that created it and append off, as in tailscale funnel --https=443 localhost:3000 off. To clear every mapping on the machine, run tailscale funnel reset.

Why an app on port 8080 still works

The port restriction applies to the public side only. The target you pass is a local address, and its port is unrestricted. tailscale funnel --bg localhost:8080 publishes https://your-machine.your-tailnet.ts.net/ on 443 and forwards each request to 127.0.0.1:8080. The daemon is acting as a reverse proxy, so your application keeps the port it already uses. Nothing in your container or your service file has to change.

What you cannot do is put an arbitrary port in the public URL. There is no https://your-machine.your-tailnet.ts.net:8080 to hand out. That matters when the client on the other end hardcodes a port number, which is normal for mail clients, database drivers, game clients and peer-to-peer sync tools. It also caps you at three public listeners on one machine. If you need more than three, give each app its own path with --set-path instead of its own port, or front them with a reverse proxy you control.

One more collision is worth knowing. The same port number cannot hold a Serve mapping and a Funnel mapping at the same time, and whichever command you ran most recently decides whether that port stays tailnet-only or becomes public. So a funnel that "stopped working" shortly after someone ran a serve command has an obvious first suspect.

What you must enable before Funnel will start

Funnel has prerequisites that are easy to miss, because none of them live in the command you are typing. You need Tailscale 1.38.3 or later, MagicDNS enabled for the tailnet, HTTPS certificates enabled for the tailnet, and the funnel node attribute granted to that machine in the tailnet policy file.

HTTPS certificates are switched on from the DNS page of the admin console. Read the warning on that page before you click it. Certificates are recorded in public certificate transparency ledgers, so your machine names and your tailnet DNS name become searchable by anyone. Tailscale's own wording: "Do not enable the HTTPS feature if any of your machine names contain sensitive information." Rename a machine called something like customer-billing-db first, because a ledger entry cannot be withdrawn later.

The node attribute is a grant in the tailnet policy file. The default policy Tailscale documents gives it to every member of the tailnet:

"nodeAttrs": [
  {
    "target": ["autogroup:member"],
    "attr":   ["funnel"],
  },
],

If someone has edited your policy file, that block may be missing or scoped to a tag. Without the attribute the node has no permission to expose anything publicly, so the funnel does not come up however correct your command is. Narrowing target to a tag such as tag:public-web means only machines carrying that tag can publish, which is the safer arrangement on a tailnet with other people on it. Funnel also needs a platform that can run the Tailscale CLI, so a Linux VPS is the easy case and a phone is not an option.

Configured is not the same as serving

A funnel that exists in the configuration and a funnel that is answering requests are two different states, and the gap between them is where the troubleshooting time goes. Check both commands.

tailscale funnel status
tailscale serve status

In tailscale funnel status, look for a public https:// URL on your .ts.net name with the local target listed beside it. If your mapping appears only in tailscale serve status, it is reachable inside the tailnet and nowhere else, and from a browser on mobile data that looks exactly like a broken funnel. Add --json to either command when a script needs to read the result.

Then check what the status output cannot tell you. The machine has to be online in the tailnet. The local service has to be listening on the address you gave it, so confirm that on the machine before blaming Funnel: confirming that a local port is actually listening takes one command and rules out the most common cause. And the mapping has to persist, because a funnel started without --bg lives only as long as the command you ran, while --bg stores it in the daemon configuration so it survives a reboot.

Test from outside at the end. A phone with Wi-Fi turned off, or curl from an unrelated server, is the only honest test. A device already in your tailnet can reach the service through Serve, so it proves nothing about the public path.

Is there a Tailscale Funnel bandwidth limit?

Yes, and Tailscale does not publish the number. The documentation says traffic sent over a Funnel is "subject to non-configurable bandwidth limits". As of September 2026 it gives no figure in megabits per second and no monthly transfer allowance. Any specific number you find in a forum thread is one person's measurement from one location at one moment, not a published limit, and nothing stops it changing.

"Non-configurable" is the important half of that sentence. You cannot raise the limit by paying more, because it is not a plan feature. The plan tiers count users and devices instead, which is a separate question answered in what the free Tailscale plan actually includes.

The traffic path explains the rest. A public visitor connects to a Funnel relay server run by Tailscale. That relay opens a TCP proxy to your machine over the Tailscale connection it already has, and your daemon passes the request to your local app. So your ceiling is shared relay capacity rather than your VPS uplink, and your latency includes the detour to whichever relay the visitor landed on. Tailscale states that the relays "do not decrypt the traffic between public devices and your device", which makes this a capacity and routing concern rather than a privacy one.

What Funnel is not built for

Do not put a media library behind a Funnel. A public Jellyfin or Plex endpoint means sustained multi-megabit transfers across shared relay capacity with no published ceiling, and the first symptom of reaching that ceiling is buffering that your users cannot explain and you cannot measure. The same reasoning rules out public file distribution and any production traffic that someone is holding you to an uptime number on.

Funnel fits the small, occasional jobs it was introduced for: receiving a webhook from GitHub, showing a work-in-progress site to someone outside your company, accepting an OAuth (open authorization) callback while you develop, or publishing a personal page with modest traffic. Those are Tailscale's own examples, and they are all low volume. None of them needs a port number in the URL.

Funnel, Serve, or a reverse proxy on a VPS

Choose by what the endpoint has to do. If a person or a service outside your tailnet must reach it over HTTPS on a standard port, use Funnel. If only your own devices need it, use Serve, which gives you the same certificate and hostname with no public exposure and no relay hop. The everyday commands are collected in a short Tailscale Serve command reference, and the full comparison is in the Serve versus Funnel decision.

If you need arbitrary ports or throughput you control, stop using Funnel and run a reverse proxy on a VPS with a public IP address. Caddy or nginx on a small server terminates TLS on any port you choose and moves data at your own network allowance. It is the same pattern used to put n8n behind HTTPS on a VPS with Docker. Tailscale still has a place in that design: the VPS joins your tailnet and proxies inward over the tunnel, which keeps it a way to avoid opening ports on a home router while the public side runs on a server you own.

FAQ

What ports can Tailscale Funnel use?

Only 443, 8443 and 10000, and only on the public side. The --https, --tcp and --tls-terminated-tcp flags all accept the same three numbers. The local target port is not restricted, so an app listening on 3000 or 8080 works without change. The public URL simply arrives on one of the three allowed ports instead.

Is there a bandwidth limit on Tailscale Funnel?

Tailscale documents "non-configurable bandwidth limits" on Funnel traffic and, as of September 2026, publishes no number for them. Because the limit is not configurable, it is not something a paid plan raises. Treat Funnel as suitable for webhooks, demos and small personal sites, and use a VPS with a public IP address for streaming or heavy download traffic.

Why is my Funnel URL not reachable from outside the tailnet?

Work through the states in order. Confirm HTTPS certificates are enabled for the tailnet and the funnel node attribute is granted to that machine in the policy file, since without either one the funnel never comes up. Then run tailscale funnel status and check that your mapping is listed there with a public URL, not only in tailscale serve status. Then confirm the machine is online, the local service is listening on the address you gave, and the mapping was created with --bg so it survived the last reboot. Also check whether a later serve command claimed the same port, because the most recent command wins.

Can I expose a service on port 8080 publicly with Funnel?

The service can keep listening on 8080 locally, and tailscale funnel --bg localhost:8080 will publish it. What you cannot get is a public URL ending in :8080, because the public listener must be 443, 8443 or 10000. If a client insists on a specific port, or you need more than three public entry points on one machine, use --set-path to separate apps by path, or run a reverse proxy on a VPS where you choose the ports.