Cloudflare Quick Tunnels: Expose Local Servers to the Internet Without a VPS
Every developer has hit this wall: you’ve got a server running on localhost:3000, and you need someone outside your network to hit it. A webhook provider needs a public URL. A client wants to see a live demo. A teammate needs to test your API from their phone. The usual answer has been ngrok, but ngrok’s free tier throttles you, rotates URLs, and gates useful features behind a paywall. Cloudflare Quick Tunnels solve the same problem, for free, with no account required, and they’ve quietly become one of the most useful tools in a backend developer’s toolkit.
This post covers what Quick Tunnels are, how they differ from full Cloudflare Tunnels, when to use them versus alternatives like ngrok or localtunnel, and how to wire them into real Node.js, Docker, and webhook-testing workflows.
What Are Cloudflare Quick Tunnels
A Quick Tunnel is a temporary, ephemeral tunnel created by the cloudflared binary that exposes a local port to a public *.trycloudflare.com URL. No Cloudflare account, no DNS setup, no zone configuration. You run one command, get a URL, and traffic routes through Cloudflare’s edge network straight to your machine.
Under the hood, cloudflared establishes an outbound connection from your machine to Cloudflare’s network. Because the connection is outbound-initiated, you don’t need to open inbound firewall ports or configure NAT — which is the same trick full Cloudflare Tunnels use for production ingress, just without the persistent hostname and dashboard config.
# Install cloudflared (macOS)
brew install cloudflared
# Install cloudflared (Linux, Debian/Ubuntu)
curl -L --output cloudflared.deb https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64.deb
sudo dpkg -i cloudflared.deb
Once installed, exposing a local server takes one line:
cloudflared tunnel --url http://localhost:3000
You’ll see output like:
+--------------------------------------------------------------------------------------+
| Your quick Tunnel has been created! Visit it at: |
| https://random-words-here.trycloudflare.com |
+--------------------------------------------------------------------------------------+
That URL is live, HTTPS-terminated, and publicly routable the moment it prints.
Quick Tunnels vs Named Cloudflare Tunnels
Cloudflare offers two very different tunnel experiences, and conflating them causes confusion in production planning.
| Feature | Quick Tunnel | Named Cloudflare Tunnel |
|---|---|---|
| Setup time | Seconds, zero config | Requires Cloudflare account + domain |
| URL | Random trycloudflare.com subdomain | Custom domain you control |
| Persistence | Ephemeral, dies when process stops | Persistent, survives restarts |
| Authentication | None | Cloudflare Access / Zero Trust policies |
| Use case | Demos, webhook testing, quick sharing | Production ingress, internal apps |
| Config file | Not needed | config.yml with ingress rules |
| DNS management | None | Managed via Cloudflare dashboard/API |
| Cost | Free, unlimited | Free tier available, scales with Zero Trust plan |
| Uptime guarantees | None — treat as disposable | Production-grade, designed for always-on services |
If you need a stable URL for a webhook integration that lives for months, use a named tunnel. If you need to show a coworker a UI bug in the next five minutes, Quick Tunnels win every time.
Setting Up a Named Tunnel for Production Use
Since Quick Tunnels are explicitly not meant for anything long-lived, it’s worth knowing the upgrade path. Named tunnels require a Cloudflare account with a zone (domain) added.
# Authenticate cloudflared with your Cloudflare account
cloudflared tunnel login
# Create a persistent named tunnel
cloudflared tunnel create my-app-tunnel
# Route a subdomain to the tunnel
cloudflared tunnel route dns my-app-tunnel app.yourdomain.com
Then define ingress rules in a config file:
# config.yml
tunnel: my-app-tunnel
credentials-file: /root/.cloudflared/<tunnel-id>.json
ingress:
- hostname: app.yourdomain.com
service: http://localhost:3000
- service: http_status:404
Run it as a persistent process:
cloudflared tunnel run my-app-tunnel
This is the same underlying mechanism as Quick Tunnels — an outbound connection to Cloudflare’s edge — but with a stable hostname, TLS cert managed automatically, and optional Zero Trust access policies layered on top.
Practical Use Case: Testing Webhooks Locally
Webhook-driven integrations (Stripe, GitHub, Twilio, Slack) are the single most common reason developers reach for tunneling tools. You can’t register localhost as a webhook endpoint, so you need a public URL that forwards to your dev machine.
Here’s a minimal Express server set up to receive a Stripe webhook, exposed via Quick Tunnel:
// server.js
import express from "express";
const app = express();
app.use(express.json());
app.post("/webhooks/stripe", (req, res) => {
console.log("Received webhook event:", req.body.type);
res.status(200).send("ok");
});
app.listen(3000, () => console.log("Listening on port 3000"));
node server.js
cloudflared tunnel --url http://localhost:3000
Take the printed trycloudflare.com URL, register it as your Stripe webhook endpoint (https://random-words.trycloudflare.com/webhooks/stripe), fire a test event from the Stripe dashboard, and watch it land in your terminal in real time. No deploy, no staging environment, no ngrok account wall.
Handling Restarts and URL Rotation
The catch with Quick Tunnels: every time you restart cloudflared, you get a new random URL. For long debugging sessions this is fine. For anything you need to reference repeatedly, script around it:
#!/bin/bash
# start-tunnel.sh
cloudflared tunnel --url http://localhost:3000 2>&1 | \
grep --line-buffered "trycloudflare.com" | \
tee tunnel-url.log
Or parse the URL programmatically in Node.js by spawning the process and reading stdout:
import { spawn } from "child_process";
const tunnel = spawn("cloudflared", ["tunnel", "--url", "http://localhost:3000"]);
tunnel.stderr.on("data", (data) => {
const output = data.toString();
const match = output.match(/https:\/\/[a-zA-Z0-9-]+\.trycloudflare\.com/);
if (match) {
console.log("Tunnel is live at:", match[0]);
// e.g. auto-update webhook registration via API here
}
});
This is exactly the kind of pattern you’d wire into a CI job that spins up an ephemeral preview environment and auto-registers it with a third-party service for integration testing.
Running Quick Tunnels Inside Docker
If your app is containerized, you can run cloudflared as a sidecar container without installing anything on the host.
# docker-compose.yml
version: "3.8"
services:
app:
build: .
ports:
- "3000:3000"
tunnel:
image: cloudflare/cloudflared:latest
command: tunnel --url http://app:3000
depends_on:
- app
docker compose up
docker compose logs tunnel | grep trycloudflare.com
This pattern is useful for demoing a fully containerized stack to stakeholders without touching your cloud provider, and it keeps the tunnel’s lifecycle tied to docker compose down — no orphaned processes hanging around exposing your machine after you’ve moved on.
Cloudflare Quick Tunnels vs ngrok vs localtunnel
| Tool | Free tier limits | Custom domains | Setup friction | Persistent URLs | Notes |
|---|---|---|---|---|---|
| Cloudflare Quick Tunnel | Unlimited, no account | No (random subdomain) | Lowest — one binary, one command | No | Backed by Cloudflare’s global edge |
| ngrok | Rate-limited, session caps | Paid only | Low, requires account + authtoken | Paid only | Rich dashboard, request replay |
| localtunnel | Unlimited but unreliable uptime | No | Very low, npm install | No | Community-run servers, frequent downtime |
| Cloudflare Named Tunnel | Unlimited | Yes, your own domain | Medium, requires zone setup | Yes | Best for production-grade exposure |
For one-off sharing and webhook debugging, Quick Tunnels beat ngrok’s free tier outright — no signup, no rate limiting on basic HTTP proxying, and it’s backed by Cloudflare’s infrastructure rather than a single relay server.
Security Considerations
Quick Tunnels expose whatever is running on that port to the entire internet the instant the tunnel is up. There’s no authentication layer by default.
- Never run a Quick Tunnel against a server with unauthenticated admin routes.
- Add your own auth middleware before exposing anything beyond a static demo.
- Treat the URL as effectively public even though it’s hard to guess — bots do scan
trycloudflare.comranges. - For anything handling real user data or payment info, use a named tunnel with Cloudflare Access policies instead.
A minimal auth guard for a Node.js server you’re about to tunnel:
app.use((req, res, next) => {
const token = req.headers["x-tunnel-secret"];
if (token !== process.env.TUNNEL_SECRET) {
return res.status(401).send("Unauthorized");
}
next();
});
Troubleshooting Common Issues
Tunnel connects but requests time out: Check that your local service is actually bound to the port you passed to --url, and that it’s listening on 0.0.0.0 or localhost, not restricted to an internal Docker network alias if you’re running cloudflared outside the container.
“failed to sufficiently increase receive buffer size” warning: Harmless on most systems, but on Linux you can silence it by tuning net.core.rmem_max:
sudo sysctl -w net.core.rmem_max=2500000
WebSocket connections drop: Quick Tunnels support WebSockets out of the box, but double-check your local server explicitly upgrades connections and isn’t blocking the Upgrade header behind a proxy layer you forgot about.
Random URL changes every restart: Expected behavior — this is a fundamental tradeoff of the “quick” tier. If it’s a blocker, invest 15 minutes setting up a named tunnel instead.
Key Takeaways
- Cloudflare Quick Tunnels expose
localhostto a public HTTPS URL with a singlecloudflared tunnel --urlcommand — no account, no DNS, no config file. - They’re ideal for webhook testing, live demos, and short-lived sharing sessions, not for production traffic.
- URLs are randomly generated and ephemeral — they change on every restart, so don’t hardcode them anywhere long-lived.
- For persistent, branded URLs with authentication, upgrade to a named Cloudflare Tunnel with Zero Trust Access policies.
- Quick Tunnels beat ngrok’s free tier for basic HTTP exposure since there’s no rate limiting or signup wall.
- Run
cloudflaredas a Docker sidecar to tunnel containerized apps without installing anything on the host. - Always add your own authentication layer before exposing anything beyond a throwaway demo — Quick Tunnels have zero built-in access control.
- The underlying mechanism (outbound-only connections to Cloudflare’s edge) is the same tech that powers Cloudflare’s production-grade Zero Trust tunneling, just without the persistence layer.
Related Articles
How GLM Built Its Own Inference Infrastructure: A Deep Dive for Backend Engineers
Learn how GLM built custom inference infrastructure for LLMs, and how Go, Docker, and Node.js engineers can apply the same scaling patterns to their own systems.
DevOpsAWS Multi-Region Disaster Recovery: Lessons from the AWS Middle East Data Loss Incident
AWS confirmed permanent data loss after Iran strikes hit Mideast facilities. Learn multi-region DR architecture patterns for Node.js, Docker, and cloud-native apps.
DevOpsCUDA for Rust: A Practical Guide to Nvidia's Native GPU Programming Support
Nvidia now supports native Rust for CUDA GPU kernels. Learn how CUDA Rust works, how it compares to C++/CUDA, and how to write your first GPU kernel.
Never Miss an Article
Stay Updated
Get new deep-dives on JavaScript, TypeScript, Go, and cloud-native engineering delivered to your reader.
Written by
Aditya RawasFull-stack engineer writing deep-dives on JavaScript, TypeScript, React, AWS, Docker, and Kubernetes. Passionate about making complex engineering concepts accessible to developers at every level.