Skip to content
Cloud & Infra Intermediate Tutorial

Give Your Local Dev Server a Stable Public HTTPS URL with Cloudflare Tunnel

Use a named Cloudflare Tunnel to expose localhost over HTTPS on your own domain — no port forwarding, no firewall rules, no certificate wrangling.

Emeka Okafor
Emeka Okafor
Security Editor · Jun 22, 2026 · 6 min read
Give Your Local Dev Server a Stable Public HTTPS URL with Cloudflare Tunnel

What You'll Build

A named Cloudflare Tunnel that exposes a local HTTP server at a persistent public HTTPS subdomain (e.g., dev.example.com) — no port forwarding, no firewall changes, and TLS provisioned automatically by Cloudflare.

Prerequisites

  • A domain managed in Cloudflare (nameservers delegated to Cloudflare — free plan works)
  • A local HTTP server running on port 3000 (adjust the port in config for anything else)
  • macOS 12+, Debian/Ubuntu, or Windows 10/11; commands shown for macOS/Linux
  • cloudflared CLI — installed in Step 1

Step 1 — Install cloudflared

macOS (Homebrew):

brew install cloudflared

Debian / Ubuntu (amd64):

curl -L -o cloudflared.deb \
  https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64.deb
sudo dpkg -i cloudflared.deb

On ARM hosts (Raspberry Pi, Ampere), use cloudflared-linux-arm64.deb instead.

Windows:

winget install Cloudflare.cloudflared

Confirm the install:

cloudflared --version

Step 2 — Authenticate cloudflared

cloudflared tunnel login

A browser window opens. Select your Cloudflare account and the zone (domain) you want to use. cloudflared saves an origin certificate to ~/.cloudflared/cert.pem and closes the prompt automatically.

Step 3 — Create the Named Tunnel

cloudflared tunnel create my-dev-tunnel

cloudflared prints a UUID — something like a1b2c3d4-5678-... — and writes credentials to ~/.cloudflared/<UUID>.json. Copy the UUID now; you need it in Step 5.

Verify it was created:

cloudflared tunnel list

Step 4 — Route a DNS Record to the Tunnel

Replace dev.example.com with the subdomain you want:

cloudflared tunnel route dns my-dev-tunnel dev.example.com

This adds a CNAME in your Cloudflare zone resolving dev.example.com<UUID>.cfargotunnel.com. No IP address or A record is needed.

Step 5 — Write the Config File

Create ~/.cloudflared/config.yml. Substitute the real UUID from Step 3 and the correct credentials path for your OS.

Linux:

tunnel: <UUID>
credentials-file: /home/<your-username>/.cloudflared/<UUID>.json

ingress:
  - hostname: dev.example.com
    service: http://localhost:3000
  - service: http_status:404

macOS: replace /home/<your-username> with /Users/<your-username>.

Windows: use forward slashes to avoid YAML escaping issues:

tunnel: <UUID>
credentials-file: C:/Users/<your-username>/.cloudflared/<UUID>.json

ingress:
  - hostname: dev.example.com
    service: http://localhost:3000
  - service: http_status:404

Three things to get right:

Field Notes
tunnel The UUID from Step 3 — the tunnel name also works, but UUID is unambiguous
credentials-file Full absolute path; cloudflared does not reliably expand ~/ here
Catch-all rule The final service: http_status:404 entry is required — cloudflared refuses to start without it

Your local server stays plain HTTP on localhost:3000. Cloudflare handles the public TLS certificate automatically.

Validate the ingress rules before running:

cloudflared tunnel ingress validate

Step 6 — Run the Tunnel

cloudflared tunnel run

Do not pass the tunnel name or UUID as an argument. Running cloudflared tunnel run my-dev-tunnel causes cloudflared to bypass your config file's ingress rules — traffic never reaches localhost:3000 and callers see a 502 Bad Gateway. With no argument, cloudflared reads the tunnel: UUID and all ingress: rules from ~/.cloudflared/config.yml automatically.

Healthy startup looks like:

INF Starting tunnel tunnelID=<UUID>
INF Connection registered connIndex=0 location=SJC
INF Connection registered connIndex=1 location=SEA
INF Connection registered connIndex=2 location=LAX
INF Connection registered connIndex=3 location=SJC

Four connections to Cloudflare's edge are expected and normal.

Verify It Works

Open https://dev.example.com in a browser — you should reach your local app.

Or from the terminal:

curl -I https://dev.example.com

Expected: HTTP/2 200 (or your app's actual status code). TLS is provisioned automatically by Cloudflare — no Certbot or Let's Encrypt setup required.

Check live tunnel status:

cloudflared tunnel list

Troubleshooting

502 Bad Gateway at the public URL Your local server isn't running or is on a different port. Test with curl http://localhost:3000. Also confirm you ran cloudflared tunnel run with no name argument (see Step 6).

No config file found or ingress rules silently ignored cloudflared couldn't locate ~/.cloudflared/config.yml. Confirm the file exists at that exact path and contains valid YAML. Run cloudflared tunnel ingress validate to surface syntax errors.

Authentication / credential errors at startup The path in credentials-file is wrong, or cert.pem is stale. Re-run cloudflared tunnel login and verify the absolute path in credentials-file points to a real file.

DNS not resolving Run dig dev.example.com to confirm the CNAME is live. Cloudflare usually propagates within 30 seconds, but local cache can delay it — test with dig @1.1.1.1 dev.example.com to bypass local resolvers.

Next Steps

  • Run at boot: sudo cloudflared service install registers the tunnel as a systemd (Linux) or launchd (macOS) service so it starts automatically.
  • Expose multiple local services: Add more hostname/service pairs to the ingress list — one tunnel can serve many subdomains.
  • Require authentication: Gate the URL with a login page via Cloudflare Zero Trust → Access → Applications.
  • Deeper reference: Cloudflare Tunnel documentation
Emeka Okafor
Written by
Emeka Okafor · Security Editor

Emeka has spent over a decade tracking threat actors, vulnerability disclosures, and the evolving landscape of application security, bringing a sharp continent-spanning perspective to his reporting. He's known for translating dense CVE advisories into clear, actionable context that developers and security teams alike actually read.

Discussion 0

Join the discussion

Sign in or create an account to comment and vote.

No comments yet

Be the first to weigh in.

Related Reading