← posts / infrastructure

Share localhost with three named people: Cloudflare's Protected Quick Tunnels vs Tailscale Funnel

cloudflared 2026.9.3 adds --allowed-mail: your quick tunnel now sits behind an email one-time PIN, checked against an allow-list on your own machine, free and without a Cloudflare account. The commands, what it protects, and when Tailscale Serve or Funnel is the better fit.

if.codesOct 5, 2026 · 4 min read#cloudflare#tailscale#tunnels#securityAI-assisted

You have a dev server on localhost:8080 and three people who need to click around it: a client, a designer, a colleague on another network. For years the quickest answer has been cloudflared tunnel --url, which hands you a random trycloudflare.com URL. It works, and it also puts an unauthenticated app on the public internet for anyone who gets hold of the link.

Adding a layer of protection to a local development server.
Adding a layer of protection to a local development server.

On 2 October Cloudflare added a fix for that: Protected Quick Tunnels. From cloudflared 2026.9.3, one extra flag puts the tunnel behind an email one-time PIN, and only addresses on your list get through. No Cloudflare account on either side, no dashboard, still free. This guide takes about ten minutes.

1. Update cloudflared

You need 2026.9.3 or later. Check what you have:

cloudflared --version

If it is older, update it with whatever installed it:

# macOS (Homebrew)
brew upgrade cloudflared

# Debian/Ubuntu installed from Cloudflare's apt repo
sudo apt-get update && sudo apt-get install --only-upgrade cloudflared

# installed as a plain binary
cloudflared update

2. Start something to share

Any local HTTP server will do. If you just want to try the flow:

mkdir -p /tmp/demo && cd /tmp/demo
echo '<h1>hello from my laptop</h1>' > index.html
python3 -m http.server 8080

3. Open a protected tunnel

In a second terminal, start the tunnel with one --allowed-mail per person:

cloudflared tunnel --url http://localhost:8080 \
  --allowed-mail alice@example.com \
  --allowed-mail bob@example.com \
  --allowed-mail carol@example.org

A wildcard works for a whole domain, for example a client's company. Quote it so your shell does not expand the *:

cloudflared tunnel --url http://localhost:8080 --allowed-mail '*@client.example'

cloudflared prints the trycloudflare.com URL as before. Send it to your three people.

4. What your visitor sees

Instead of your app, the visitor gets a sign-in page asking for an email address. Cloudflare Access sends a one-time PIN to that mailbox; entering it proves they own the address. Then the decision happens on your machine: cloudflared compares the verified address with the list you passed on the command line and lets the request through or not. Cloudflare's post puts it plainly: your guest list never leaves your machine.

The email one-time PIN acts as a gatekeeper for the tunnel.
The email one-time PIN acts as a gatekeeper for the tunnel.
Need the backend underneath done properly?I build and harden Go and Python backends. The estimate is free.

What it protects, and what it does not

From the announcement and the Quick Tunnels docs, the important details are:

  • Sessions last up to four hours, less if the visitor's Access sign-in expires sooner, and end the moment you stop cloudflared. Stopping the process is your kill switch.
  • The allow-list lives in memory on your machine. Change it by restarting the tunnel with different flags. That also means a new random URL.
  • Access credentials are stripped before the request is forwarded to your app, so your app is not handed the visitor's sign-in token.
  • It proves an email address, nothing more. If Bob forwards the PIN email or signs in on a shared machine, the tunnel cannot tell. Your app's own login, if it has one, still matters.
  • Quick Tunnel limits still apply: up to 200 in-flight requests per tunnel (more get a 429), no Server-Sent Events, and no uptime guarantee. If your app streams with SSE, for example a chat UI or live logs, it will not work through a quick tunnel, protected or not.

For a demo, a review or a quick look at a webhook payload page, that is a big improvement over a public, unauthenticated URL, at the cost of one flag.

When Tailscale is the better fit

Tailscale has two related commands, and they answer different questions.

Tailscale Serve: share inside your tailnet

tailscale serve 8080

This publishes the local port over HTTPS to devices in your tailnet only. If the people you are sharing with are already on your tailnet, such as teammates or your own phone, this is the better option: no PINs, no four-hour limit, and the app is never on the public internet. People outside your tailnet need to join it or have the device shared with them, which means a Tailscale account and client on their side.

Tailscale Funnel: public, without the allow-list

tailscale funnel 8080

Funnel routes traffic from the public internet to your local service, and visitors do not need Tailscale accounts. That makes it the closest match to a quick tunnel, but Funnel itself adds no sign-in step: anyone with the URL gets in, and your app has to do its own authentication. Funnel also has setup requirements: MagicDNS and HTTPS certificates enabled for the tailnet, a funnel node attribute in your tailnet policy file, and only ports 443, 8443 and 10000 can be exposed. In exchange you get a stable ts.net hostname instead of a new random URL every time.

Which one to use

  • A few named outsiders, for an afternoon: Protected Quick Tunnel. One command, no accounts, email-gated, gone when you hit Ctrl-C.
  • Teammates who already run Tailscale: tailscale serve. Private by default, no time limit.
  • A public endpoint with a stable URL, for example a webhook receiver that a third-party service must reach: Tailscale Funnel, or a named Cloudflare Tunnel with an Access policy. Both need an account. Neither a protected quick tunnel nor an email PIN works for a machine caller anyway.
  • Anything that streams with SSE or needs to stay up: not a quick tunnel.

The habit worth changing today is small: if you already type cloudflared tunnel --url, add --allowed-mail every time. A dev server reachable only by the three people you meant to share it with is a much easier thing to defend.

Choosing between private network sharing and public internet access.
Choosing between private network sharing and public internet access.

Sources

if.codesI build RAG, AI integrations and agent pipelines on Go and Python backends — and write about it here.
// keep reading

More posts on AI and backends.

// free quote

Read something you need? I’ll quote it for free.

RAG, AI integrations, agents or the backend underneath — tell me what you have and what should change. I read every request myself.

Free · no commitment

Tell me what you have. I’ll tell you what it takes.

1Describe the projectA few sentences is enough — about two minutes.
2I review itI read it myself and may ask a follow-up question.
3You get a free quoteScope, approach and estimate — yours to keep, no strings.
What kind of project is it?
Free and without obligation. Your details are used only to reply — see the privacy policy.