Configuring Cloudflare Tunnel to Expose Servers for Local Development, Webhooks etc
I needed a way to expose local dev servers to the internet for webhook testing. ngrok works, but Cloudflare Tunnel is free and lets you use your own domain whereas ngrok is free, but you pay $10 (as of 20251201) per custom domain.
Prerequisite: Cloudflare must already be managing DNS for your domain.
Install and Login
brew install cloudflared
cloudflared tunnel login
The login command opens your browser to authenticate with Cloudflare.
Create a Tunnel
You only need one tunnel. You can route multiple subdomains through the same tunnel. I’ll use dev as the tunnel name in these examples:
cloudflared tunnel create dev
This creates the tunnel and stores credentials at ~/.cloudflared/<tunnel-id>.json.
Create DNS Routes
This creates CNAME records pointing your subdomains to the tunnel:
cloudflared tunnel route dns dev dev.yourdomain.com
cloudflared tunnel route dns dev dev-backend.yourdomain.com
Tip: Keep it simple—use subdomains under the same root domain even for different projects. Instead of dev.project1.com and dev.project2.com, use dev.yourdomain.com and dev2.yourdomain.com. Otherwise, you’d find the cloudflared tunnel route dns <name> <subdomain> command creating CNAME records in your first domain. It’s apparently not supported unless you use the web dashboard to manage your tunnels. It’s much simpler just to stick to 1 root domain.
To remove a route, delete the CNAME record from the Cloudflare dashboard.
Configure and Run
Create ~/.cloudflared/config.yml:
tunnel: <tunnel-id>
credentials-file: /Users/you/.cloudflared/<tunnel-id>.json
ingress:
- hostname: dev.yourdomain.com
service: http://localhost:5174
- hostname: dev-backend.yourdomain.com
service: http://localhost:4002
- service: http_status:404
The last rule is a required catch-all for unmatched requests.
Then normally, you’d just run:
cloudflared tunnel run dev
Both subdomains now route to their respective local ports.