DevOps September 19, 2026 Aditya Rawas 7 min read

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.

FeatureQuick TunnelNamed Cloudflare Tunnel
Setup timeSeconds, zero configRequires Cloudflare account + domain
URLRandom trycloudflare.com subdomainCustom domain you control
PersistenceEphemeral, dies when process stopsPersistent, survives restarts
AuthenticationNoneCloudflare Access / Zero Trust policies
Use caseDemos, webhook testing, quick sharingProduction ingress, internal apps
Config fileNot neededconfig.yml with ingress rules
DNS managementNoneManaged via Cloudflare dashboard/API
CostFree, unlimitedFree tier available, scales with Zero Trust plan
Uptime guaranteesNone — treat as disposableProduction-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

ToolFree tier limitsCustom domainsSetup frictionPersistent URLsNotes
Cloudflare Quick TunnelUnlimited, no accountNo (random subdomain)Lowest — one binary, one commandNoBacked by Cloudflare’s global edge
ngrokRate-limited, session capsPaid onlyLow, requires account + authtokenPaid onlyRich dashboard, request replay
localtunnelUnlimited but unreliable uptimeNoVery low, npm installNoCommunity-run servers, frequent downtime
Cloudflare Named TunnelUnlimitedYes, your own domainMedium, requires zone setupYesBest 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.com ranges.
  • 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 localhost to a public HTTPS URL with a single cloudflared tunnel --url command — 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 cloudflared as 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.

Never Miss an Article

Stay Updated

Get new deep-dives on JavaScript, TypeScript, Go, and cloud-native engineering delivered to your reader.

Aditya Rawas

Written by

Aditya Rawas

Full-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.