Article·  

Cloudflare Tunnels for Dev Teams — A Beginner's Guide

Learn how to give every developer on your team a permanent public URL for local development using Cloudflare Tunnels. Perfect for webhooks, OAuth callbacks, and mobile testing.

When you're building a web app locally, there are moments where localhost isn't enough. OAuth providers need a real callback URL. Stripe needs to send webhooks somewhere. A client wants to preview your work-in-progress. This is where Cloudflare Tunnels come in. Once you set one up, you'll wonder how you lived without it.

This guide covers everything from first principles: what tunnels are, how they work, and how to give every developer on your team their own permanent public URL in under 10 minutes.

What Is a Cloudflare Tunnel?

A Cloudflare Tunnel creates an outbound-only connection from your machine to Cloudflare's edge network. There's no need to open firewall ports, configure NAT, or touch your router. Your local server is simply reachable at a real HTTPS URL. It's permanent.

Your machine (localhost:3000)
        ↕ encrypted outbound connection
Cloudflare edge (alice.yourdomain.com)
        ↕
The internet

Two files live on each developer's machine. They never go in the repo:

FileWhat it does
~/.cloudflared/cert.pemAccount-level credential that allows creating/deleting tunnels
~/.cloudflared/<UUID>.jsonTunnel-specific credential that allows running that one tunnel

Prerequisites

  • A domain managed by Cloudflare (free plan works fine)
  • Access to the team's Cloudflare account (ask your account owner to invite you under Manage Account → Members)
  • macOS with Homebrew (Linux/Windows instructions are in the Cloudflare docs)

One-Time Setup Per Developer

1. Install cloudflared

brew install cloudflared

Verify it works:

cloudflared --version

2. Authenticate with your Cloudflare account

cloudflared tunnel login

This opens a browser window. Log in to Cloudflare and select the domain your team uses (e.g. yourdomain.com). A cert.pem file is saved at ~/.cloudflared/cert.pem. This is your account credential and stays on your machine.

3. Create your personal tunnel

Pick a name that identifies you, for example alice-appname.

cloudflared tunnel create alice-appname

The output includes a UUID. Copy it, as you'll need it in the next step. A credentials file is saved automatically at ~/.cloudflared/<UUID>.json.

4. Create a permanent DNS subdomain

cloudflared tunnel route dns alice-appname local-alice.yourdomain.com

This creates a CNAME record in Cloudflare DNS that points local-alice.yourdomain.com to your tunnel. It's permanent: do this once and the subdomain is yours.

5. Create your local config file

Your project should include a template at .cloudflared/config.template.yml. Copy it:

cp .cloudflared/config.template.yml .cloudflared/config.yml

Then fill in your values:

tunnel: <YOUR-UUID>
credentials-file: ~/.cloudflared/<YOUR-UUID>.json

ingress:
  - hostname: local-alice.yourdomain.com
    service: http://localhost:3000
  - service: http_status:404

config.yml is gitignored and lives only on your machine.

6. Validate your setup

# Check the ingress rules parse correctly
cloudflared tunnel --config .cloudflared/config.yml ingress validate

# Confirm your tunnel appears in the list
cloudflared tunnel list

Daily Usage

Start these in two separate terminals each time you develop:

# Terminal A — your app
pnpm dev

# Terminal B — the tunnel
pnpm dev:tunnel

Your app is now live at https://local-alice.yourdomain.com with a valid TLS certificate.

Verify it's reachable.

curl -I https://local-alice.yourdomain.com

You should get HTTP/2 200 (or whichever status your app returns for /). You should not see a 503 from Cloudflare.


Registering Your Permanent URL for Webhooks and OAuth

Your subdomain never changes. Register it once and forget about it.

OAuth Callbacks (Google, GitHub, etc.)

Add your tunnel URL as an authorised redirect URI in each provider's developer console:

  • Google Cloud Console → Credentials → OAuth Client → Authorised redirect URIs: https://local-alice.yourdomain.com/auth/callback/google
  • GitHub → Settings → Developer settings → OAuth Apps → Authorization callback URL: https://local-alice.yourdomain.com/auth/callback/github

Stripe / Polar Webhooks

In your payment provider's dashboard, add a webhook endpoint:

https://local-alice.yourdomain.com/api/webhooks/stripe

Every developer registers their own subdomain in their own test-mode account. No shared secrets, no collisions.


What Goes in the Repo

FileCommitted?Purpose
.cloudflared/config.template.yml✅ YesPlaceholder template for new team members
.cloudflared/config.yml🚫 No (gitignored)Each dev's personal tunnel config
~/.cloudflared/<UUID>.json🚫 NeverTunnel credentials that stay on your machine
~/.cloudflared/cert.pem🚫 NeverAccount credential that stays on your machine

How the Account Owner Adds a Team Member

  1. Go to dash.cloudflare.com → Manage Account → Members
  2. Invite by email with the Cloudflare Tunnel permission (or Administrator for full access)
  3. The new member runs cloudflared tunnel login, selects the shared domain, and follows steps 3–6 above

Each person ends up with their own tunnel, their own subdomain, and their own credentials. Nobody shares secrets. Nobody steps on anyone else's tunnel.


Troubleshooting

cloudflared tunnel list fails / cert.pem not found Run cloudflared tunnel login again to re-authenticate.

pnpm dev:tunnel fails with "credentials file not found" Re-run cloudflared tunnel create <name>. The .json file was not created or deleted.

Public URL returns 503 Service Unavailable Check that pnpm dev is running and started on the expected port. Verify which port Nuxt is using and update service: in config.yml accordingly.

Nuxt started on port 3001 (expecting 3000) Something else is occupying port 3000. Free the port, or update the service: field in .cloudflared/config.yml to match:

service: http://localhost:3001

ERR_TOO_MANY_REDIRECTS on the tunnel URL This happens when your app forces HTTPS but Cloudflare is sending HTTP to the origin. Cloudflare Tunnels already handle TLS at the edge. Set originServerName or ensure the service: in config.yml uses http://, not https://.


Summary

StepWhoFrequency
Invite team member to Cloudflare accountAccount ownerOnce per person
cloudflared tunnel loginDeveloperOnce (or after cert.pem expires)
cloudflared tunnel create + route dnsDeveloperOnce per machine
Copy config.template.yml → config.yml and fill in valuesDeveloperOnce per machine
pnpm dev + pnpm dev:tunnelDeveloperEvery dev session

Cloudflare Tunnels solve the localhost problem permanently. There are no ngrok subscriptions, no random URLs that change on restart, and no firewall headaches. A stable HTTPS address on your own domain is ready whenever you are.

Copyright © 2026