{
 "kind": "feature",
 "id": "feature:tunnels",
 "url": "https://headlesson.com/features/tunnels",
 "title": "Sharing a local service on the internet: three recorded tunnel clients and what was not tested",
 "summary": "For assistants asked to put a local demo or service on a public URL, this feature sets out the three recorded tunnel clients side by side and separates what headlesson ran from cautions taken from their documentation. headlesson's tests on macOS on 2026-10-11 stopped before any tunnel was opened: they ran only version and help commands (and, for ngrok, a local configuration check), so no public URL, account, access policy or traffic was tested.",
 "published": "2026-10-14",
 "updated": "2026-10-14",
 "body": "# Pick by who should reach the service\n\n- A throwaway public URL with no account, for a short test: app:cloudflare-tunnel, whose Quick Tunnel (cloudflared tunnel --url <local-URL>) gives a trycloudflare.com address and needs no account or domain. Its documentation describes Quick Tunnels as for tests and development.\n- A public URL with access rules in front of it: app:ngrok, an outbound agent that needs an owner-created authtoken; its documentation describes Traffic Policy rules such as OAuth, domain restrictions or deny rules applied to the endpoint.\n- A public URL when the person already runs a logged-in Tailscale client: app:tailscale-funnel publishes a local port on the internet under the tailnet's ts.net name; its lesson points to Tailscale Serve instead when only tailnet members should reach the service.\n- Editorial advice, not tested: all three make the service reachable from the internet once the tunnel is up, so decide the audience, the data that may be seen and when sharing stops before choosing among app:cloudflare-tunnel, app:ngrok and app:tailscale-funnel. headlesson has not compared their speed, reliability or prices.\n\n# Observed in the tests\n\n- No tunnel was opened in the tests of app:cloudflare-tunnel, app:ngrok or app:tailscale-funnel. headlesson's test rules forbid exposing anything to the internet, creating accounts and paying, so each lesson stopped at its first step that would create a public URL or needs an account.\n- app:ngrok: the official standalone macOS arm64 binary, version 3.39.11, ran ngrok version, ngrok help and a local configuration check with network and home-directory access denied; the lesson stopped where the owner must supply an authtoken.\n- app:cloudflare-tunnel: an already-installed cloudflared 2026.3.0 ran its version and tunnel help commands; the help lists --url with a default of http://localhost:8080.\n- app:tailscale-funnel: an already-installed tailscale 1.102.2 ran its version and funnel help commands; the help lists funnel <target>, status [--json] and reset. tailscale status was not run because it shows device names on the person's network.\n\n# Cautions from the documentation\n\n- app:cloudflare-tunnel: Quick Tunnels are for tests and development, with no uptime guarantee, a limit of 200 in-flight requests and no server-sent events; optional email access restrictions need an interactive browser login.\n- app:ngrok: an endpoint without a policy is reachable from the internet, and Traffic Policy can apply OAuth, domain restrictions or deny rules; the agent's local inspection API at http://127.0.0.1:4040/api has no authentication and must not be exposed.\n- app:tailscale-funnel: Funnel is in beta, serves TLS only, uses the tailnet's ts.net name and only listener ports 443, 8443 and 10000; on macOS it needs the open-source client variant. Serve and Funnel on the same port follow the last command run, so a private Serve can turn into a public Funnel, and a tailnet login does not authenticate public visitors.\n\n# headlesson's own reading, not tested\n\n- app:cloudflare-tunnel: anyone who has an unrestricted generated URL can reach the service; access restrictions were not tested. The Apache-2.0 license recorded for it covers the cloudflared client package, not Cloudflare's hosted network.\n- app:ngrok: traffic and SIEM logging is configured request logging, and the record alone does not show a complete administrator audit history; check which policy features the person's plan includes, as they are not all free.\n\n# A safe sequence\n\nWith app:cloudflare-tunnel, app:ngrok or app:tailscale-funnel: agree with the person on the exact local port, the audience and what data the service shows; let the person set up any account, authtoken or tailnet approval themselves; open the tunnel, check that the URL serves only the intended service, and tell the person the URL; stop the tunnel when sharing should end (stop the process, or tailscale funnel reset) and confirm the URL no longer answers. Each lesson (headlesson_get with sections [\"lesson\",\"gotchas\"]) gives the commands and the version tested.\n",
 "refs": [
  {
   "id": "app:cloudflare-tunnel",
   "name": "Cloudflare Tunnel (cloudflared client)",
   "source": {
    "source": "https://developers.cloudflare.com/cloudflare-one/networks/connectors/cloudflare-tunnel/",
    "checked": "2026-10-09",
    "certainty": "primary",
    "by": "official"
   }
  },
  {
   "id": "app:ngrok",
   "name": "ngrok",
   "source": {
    "source": "https://ngrok.com/docs/agent/",
    "checked": "2026-10-09",
    "certainty": "primary",
    "by": "official"
   }
  },
  {
   "id": "app:tailscale-funnel",
   "name": "Tailscale Funnel (open-source client)",
   "source": {
    "source": "https://tailscale.com/docs/features/tailscale-funnel",
    "checked": "2026-10-09",
    "certainty": "primary",
    "by": "official"
   }
  }
 ],
 "article_source": {
  "source": "headlesson editorial judgement",
  "checked": "2026-10-14",
  "certainty": "inferred",
  "by": "editorial"
 },
 "note": "Editorial article by the headlesson operator, written from the catalog records it cites. Test results in it are the operator's own observations, not vendor claims. Each cited record keeps its own source; the article's editorial source does not stand in for them.",
 "mcp_get": "headlesson_get id=\"feature:tunnels\"",
 "page_updated": "2026-10-13"
}
