use-cloudflare-tunnel
Skill13 starsChanged 13 days agoMITView on GitHub
Use if exposing localhost ports or multi-service apps via Cloudflare Tunnel for public URLs, remote testing, webhooks, or previewing without port forwarding.
We split this skill into the rules it gives an agent
5 rules. About 0 are common, 3 are distinctive, and 2 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈3
Distinctive · estimate
This author's own choices. Worth a look.
2
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- could be automatic: a setting could enforce it
- Distinctive: line 26
Same-Origin Unified Proxy Pattern: When testing Single Page Applications (React, Expo Web, Vite) that consume local APIs (Supabase, …
- Distinctive: line 30
Never report a tunnel URL to a user or open a browser on a remote machine until all 3 gates pass:
- Very specific: line 132
When a new *.trycloudflare.com subdomain is assigned, public authoritative edge DNS requires 1–3s to propagate.
- Very specific: line 197
Never run a blunt pkill -f "cloudflared tunnel" or delete ~/.cloudflared/ during quick tunnel cleanup!
- Distinctive: line 227
Every topic has an exhaustive reference document.
≈ “Common” is an estimate: at least 1 in 5 of the 308,232 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Cloudflare Tunnel
Expose local development ports, containers, and multi-service architectures to the public internet securely using Cloudflare Tunnel without opening firewall ports.
Purpose & Mental Model
AI agents often develop applications inside isolated environments (Docker containers, cloud VMs, WSL, local dev servers) that cannot be directly reached by external services, physical mobile devices, or remote browser controllers like ego-browser.
Cloudflare Tunnel establishes an outbound encrypted connection (QUIC or HTTP/2 over port 7844) from your local environment to Cloudflare's global edge network:
- Quick Tunnels (
try.cloudflare.com): Zero-configuration, ephemeral public HTTPS URLs. No Cloudflare account, API tokens, or DNS required. Features native agent support via--output json. Best for ad-hoc agent testing, webhooks, and remote browser validation.- Protected Quick Tunnels (
--allowed-mail): Supported incloudflared >= 2026.10.0(Cloudflare Birthday Week). Allows locking the tunnel behind email OTP authentication without an account or dashboard configuration. - Agent Trade-offs & Limitations:
- Pros: Zero secret leakage risk and instant one-command setup.
- Cons: Subdomains rotate on restart (
*.trycloudflare.com), capped at 200 concurrent in-flight requests (returnsHTTP 429), and does not support Server-Sent Events (SSE) (text/event-streamstalls or drops tokens). - Headless Wall:
--allowed-mailrequires an interactive browser session to enter the OTP code; it blocks headless API clients,curl, and automated agent-to-agent interactions.
- Protected Quick Tunnels (
- Named Tunnels (Production & Persistent): Persistent, authenticated tunnels tied to custom domains and Cloudflare Zero Trust Access policies.
- Agent Suitability: Ideal for long-lived environments, automated agent-to-agent programmatic access via Cloudflare Access Service Tokens, high-concurrency workloads, and streaming SSE (LLM chat streaming).
- Native Ingress: Eliminates the need for a reverse proxy like
unified-proxy.mjsby natively routing multiple services (e.g./-> frontend,/api-> backend) inconfig.yml.
- Same-Origin Unified Proxy Pattern: When testing Single Page Applications (React, Expo Web, Vite) that consume local APIs (Supabase, Express, FastAPI) over Quick Tunnels, running a lightweight unified reverse proxy on one port eliminates browser Mixed Content (
https->http) and CORS blocks.
The Mandatory 3-Gate Verification Rule
Never report a tunnel URL to a user or open a browser on a remote machine until all 3 gates pass:
┌────────────────────────────────────────────────────────────────────────┐
│ Gate 1: Local Origin Health │
│ curl -s -f http://127.0.0.1:<PORT>/<path> ──> Must return HTTP 200 │
└───────────────────────────────────┬────────────────────────────────────┘
▼
┌────────────────────────────────────────────────────────────────────────┐
│ Gate 2: Global DNS Publication (60s SOA Negative Cache Window) │
│ dig @1.1.1.1 +short <subdomain> ──> Must return Edge IPs │
└───────────────────────────────────┬────────────────────────────────────┘
▼
┌────────────────────────────────────────────────────────────────────────┐
│ Gate 3: Target Client Reachability (Zero False Positives) │
│ ssh <client> "curl -s -o /dev/null -w '%{http_code}' <URL>" ──> 200 OK│
└────────────────────────────────────────────────────────────────────────┘Decision Tree
flowchart TD
Start["Task: Expose local service"] --> NeedAuth{"Requires custom domain or auth?"}
NeedAuth -->|No: Ad-hoc / Testing| TargetOrigin{"Origin Type?"}
NeedAuth -->|Yes: Persistent / Production| NamedTunnel["Read references/named-tunnels.md
Use cloudflared tunnel run --token"]
TargetOrigin -->|UI Design / Theme / Hydration Fidelity| BuiltInstance["Build production local instance
(e.g., pnpm preview on 8788)"]
TargetOrigin -->|Rapid Loop / API Debugging| DevInstance["Run dev server (e.g., 4321 / 3000)"]
BuiltInstance --> IsFullStack{"SPA calling local backend API?"}
DevInstance --> IsFullStack
IsFullStack -->|Yes: Web + API| Unified["Read references/same-origin-proxy.md
Run scripts/unified-proxy.mjs
Then tunnel proxy port"]
IsFullStack -->|No: Single Port Service| QuickTunnel["Read references/quick-tunnels.md
Run scripts/quick-tunnel.sh --port <P>"]
QuickTunnel --> VerifyTarget{"Client / Remote Target?"}
Unified --> VerifyTarget
VerifyTarget -->|MacBook / Remote Host| TargetProbe["Read references/remote-testing.md
Execute Gate 3: Target Client Probe"]
VerifyTarget -->|ego-browser / Mobile| Remote["Read references/remote-testing.md
Drive CDP with 390x844 viewport"]
VerifyTarget -->|Standard webhook| Done["Inspect public trycloudflare.com URL"]Minimal Reading Sets
Load only the reference files required for your specific path:
| Scenario | Read First | Supporting |
| Quick ad-hoc tunnel for single port | references/quick-tunnels.md | references/daemon-lifecycle.md |
| Frontend SPA + Backend API (Expo, React, Supabase) | references/same-origin-proxy.md | references/quick-tunnels.md |
Remote browser testing (MacBook open, ego-browser, mobile) | references/remote-testing.md | references/daemon-lifecycle.md |
| Production named tunnel with custom domain & DNS | references/named-tunnels.md | references/ingress-rules.md |
| Tunnel fails, 502, NXDOMAIN lock, or UDP blocked | references/troubleshooting.md | references/networking-protocols.md |
Quick Starts
1. Expose a Single Local Port with Process Group Isolation
Using the bundled automated script:
# Starts tunnel detached, extracts URL, verifies DNS (auto-handles macOS/Linux)
bash scripts/quick-tunnel.sh --port 8080 --out /tmp/tunnel-url.txtOr via raw CLI with agent-safe process isolation and native --output json:
# 1. Clean previous state (DO NOT delete ~/.cloudflared which stores production certs!)
rm -f /tmp/cloudflared-8080.log /tmp/cloudflared-8080.pid
# 2. Launch detached daemon (auto-fallback across Linux setsid / macOS nohup)
CF_ARGS=(tunnel --url "http://127.0.0.1:8080" --protocol auto --logfile /tmp/cloudflared-8080.log --pidfile /tmp/cloudflared-8080.pid --no-autoupdate --output json)
if command -v setsid &>/dev/null; then
setsid nohup cloudflared "${CF_ARGS[@]}" </dev/null >/dev/null 2>&1 &
else
nohup cloudflared "${CF_ARGS[@]}" </dev/null >/dev/null 2>&1 &
fi
# 3. Extract URL (or parse json events)
sleep 3
TUNNEL_URL=$(grep -o 'https://[-a-z0-9.]*trycloudflare.com' /tmp/cloudflared-8080.log | tail -n 1)2. The 2-Step DNS Handshake (Preventing 60-Second Negative-Cache Delays)
When a new *.trycloudflare.com subdomain is assigned, public authoritative edge DNS requires 1–3s to propagate. Querying client/local DNS (e.g. Tailscale MagicDNS 100.100.100.100) prematurely causes an immediate 60-second RFC 2308 negative-cache lock (Cloudflare SOA minimum TTL).
HOST_ONLY=$(echo "$TUNNEL_URL" | sed -E 's#^https?://##')
# Step A: Wait for global authoritative DNS (1.1.1.1 & 8.8.8.8)
for i in {1..15}; do
IP1=$(dig @1.1.1.1 +short "$HOST_ONLY" 2>/dev/null | tail -n 1)
IP2=$(dig @8.8.8.8 +short "$HOST_ONLY" 2>/dev/null | tail -n 1)
if [[ -n "$IP1" && -n "$IP2" ]]; then break; fi
sleep 1
done
# Step B: Flush client DNS cache before first query
if command -v dscacheutil &>/dev/null; then
dscacheutil -flushcache 2>/dev/null || true
fi
# Step C: Verify directly from target client machine (Target Perspective Gate)
# e.g., ssh <client> "curl -s -L -o /dev/null -w '%{http_code}' -m 5 '$TUNNEL_URL'" # Returns 200 or 3xx redirect3. Full-Stack Web + API (Unified Same-Origin Proxy)
Solves the browser Mixed Content block where an HTTPS tunnel cannot talk to http://127.0.0.1:<api-port>:
# 1. Start unified proxy on port 8099
node scripts/unified-proxy.mjs \
--port 8099 \
--static /tmp/web-dist \
--api-port 55721 \
--api-prefixes /api/,/auth/,/rest/,/functions/ &
# 2. Expose the unified proxy with isolated daemon
bash scripts/quick-tunnel.sh --port 8099 --out /tmp/public-app.txtKey Patterns
Pattern A: Cross-Platform Process-Isolated Daemon Spawning
Decouple the tunnel daemon from the tool invocation process group:
CF_ARGS=(tunnel --url "http://127.0.0.1:${PORT}" --protocol auto --logfile "$LOGFILE" --output json)
if command -v setsid &>/dev/null; then
setsid nohup cloudflared "${CF_ARGS[@]}" </dev/null >/dev/null 2>&1 &
else
nohup cloudflared "${CF_ARGS[@]}" </dev/null >/dev/null 2>&1 &
fiPattern B: Protocol Negotiation (auto vs http2)
While Named Tunnels default to auto, ad-hoc quick tunnels (cloudflared tunnel --url ...) internally default to quic unless --protocol auto is explicitly supplied. Always pass --protocol auto so cloudflared automatically falls back to HTTP/2 over TCP port 7844 if UDP is blocked by firewalls:
# Recommended: Auto-negotiate QUIC with HTTP/2 fallback
cloudflared tunnel --protocol auto --url http://127.0.0.1:8080
# Or force pure HTTP/2 if UDP 7844 is strictly prohibited:
cloudflared tunnel --protocol http2 --url http://127.0.0.1:8080Pattern C: Clean Teardown (Preserving Named Tunnels)
Never run a blunt pkill -f "cloudflared tunnel" or delete ~/.cloudflared/ during quick tunnel cleanup! That kills production named tunnels and destroys credentials. Clean only ad-hoc quick tunnel instances and temporary logs:
# Safely kill only ad-hoc quick tunnels (Preserves named tunnels):
pkill -f "cloudflared tunnel --url" || true
rm -f /tmp/cloudflared*Common Pitfalls
| Pitfall | Impact | Fix |
Blunt pkill -f "cloudflared tunnel" | Kills production named tunnels running on the same host! | Use pkill -f "cloudflared tunnel --url" or kill by recorded PID. |
Wiping ~/.cloudflared/ | Destroys permanent named tunnel cert.pem and <UUID>.json keys! | Quick tunnels do NOT use ~/.cloudflared/. Delete only /tmp/cloudflared-*. |
| Streaming SSE over Quick Tunnels | LLM chat tokens or Vite HMR buffer indefinitely or drop. | Quick Tunnels do NOT support SSE. Use Named Tunnels for streaming. |
| Exceeding 200 in-flight requests | Quick Tunnels return HTTP 429 Too Many Requests. | Quick Tunnels enforce a hard 200 concurrency cap. Use Named Tunnels for load. |
Automating tests with --allowed-mail | Headless curl, agents, and webhooks get blocked by interactive OTP redirect. | --allowed-mail requires an interactive browser. Use Named Tunnels + Service Tokens for agents. |
| Assuming HTTP/2 runs on port 443 | Firewall still blocks tunnel even with --protocol http2. | Open outbound destination port 7844 TCP & UDP (tunnel data plane). Port 443 is control plane only. |
Hardcoding setsid on macOS | Silent failure when setsid is absent, causing 20s script timeouts. | Use if command -v setsid; then setsid ...; else nohup ...; fi. |
Hardcoding glob path: /v2/* in ingress | Fails to match subpaths because path is evaluated as Go regex. | Use Go regex path: /v2/.* or path: ^/v2/. |
| Probing tunnel from origin container only | False-positive 200 OK while remote user gets connection failure or DNS error. | Mandate Target Client Perspective Probe (curl == 200 from client network stack). |
| Early DNS query before edge propagation | Client resolver caches negative answer for 60s SOA TTL. | Wait for 1.1.1.1 & 8.8.8.8 NOERROR first, then flush client DNS. |
| Tunneling only frontend SPA port | Browser blocks API calls as Mixed Content or ERR_CONNECTION_REFUSED. | Use references/same-origin-proxy.md and scripts/unified-proxy.mjs. |
Missing --no-autoupdate | cloudflared hangs or restarts during agent tool execution. | Always include --no-autoupdate on startup. |
Reference Routing Table
Every topic has an exhaustive reference document. Read the relevant file when performing detailed work:
| Reference File | Read When |
references/quick-tunnels.md | Setting up ephemeral development tunnels on trycloudflare.com, --output json agent mode, DNS propagation timings, and CLI flags. |
references/named-tunnels.md | Setting up persistent production tunnels with Cloudflare Zero Trust, tokens, custom domains, and systemd services. |
references/ingress-rules.md | Writing config.yml ingress rules, Go regex path routing, originRequest timeouts, TLS verification, and validation commands. |
references/same-origin-proxy.md | Resolving Mixed Content and CORS errors when exposing full-stack SPA + API architectures (React, Expo Web, Supabase). |
references/remote-testing.md | Driving ego-browser, target client verification gates, and external webhook testing. |
references/networking-protocols.md | Understanding QUIC/HTTP/2 over port 7844, Post-Quantum hybrid key exchange, and running cloudflared tunnel diag. |
references/daemon-lifecycle.md | Managing background cloudflared daemons cross-platform, PID tracking, metrics endpoints, and safe teardown. |
references/troubleshooting.md | Diagnosing 502 Bad Gateway, 1033 Argo Tunnel error, 60s SOA negative cache locks, and false-positive probe failures. |
Verification & Orphan Check
Validate that the skill follows the specification:
for f in $(find references -name '*.md' -type f); do grep -q "$(basename "$f")" SKILL.md || echo "ORPHAN: $f"; doneMust return 0 orphans.
In this repository31 other files it gives its agents.
- AGENTS.mdAGENTS.md
- audit-agentic-cliSkill
- audit-agentic-mcpSkill
- audit-completionSkill
- audit-skill-by-derailmentSkill
- audit-ui-uxSkill
- build-chrome-extensionSkill
- build-cloudflare-access-ssoSkill
- build-cloudflare-email-sendingSkill
- build-kernel-ts-sdkSkill
- build-licenseseat-swiftSkill
- build-mcp-use-serverSkill
- build-raycast-script-commandSkill
- build-skillSkill
- ci-cd-optimizeSkill
- convert-to-natural-writingSkill
- convert-url-to-nextjsSkill
- init-agent-configSkill
- init-makefilesSkill
- mobilerun-controlSkill
- optimize-nextjs-fluiditySkill
- publish-npm-packageSkill
- run-astro-auditSkill
- run-railwaySkill
- run-repo-cleanupSkill
- run-ts-cleanupSkill
- test-by-maestroSkill
- test-by-mcpc-cliSkill
- update-agent-configSkill
- use-chatgpt-by-applescriptSkill
- use-linear-cliSkill
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

