PYYUPSK

a self-taught dev from Thailand

Pongsakorn Thipayanate · Samutsakhon, TH ·webring

tutorial

Stop Fighting localhost: Stable Dev URLs with Cloudflared

This guide covers stable HTTPS URLs for local development with Cloudflare Tunnel. It stops webhooks from breaking after every restart.

view .mdopen in claudeopen in chatgpt

Local development stays easy until it does not.

Once your project touches webhooks, OAuth callbacks, or third-party platforms like LINE Official Account, http://localhost becomes useless. It has no HTTPS, no public access, and no stability.

I fought this problem across multiple projects. Eventually, I stopped treating it as a temporary inconvenience and fixed it properly. Cloudflare Tunnel turned out to be the cleanest solution.

The Localhost Wall

Many platforms refuse to talk to localhost at all.

The LINE Messaging API, payment gateways, OAuth providers, and most webhook-based services expect one thing: a public HTTPS URL. They do not care that you are only developing. From their perspective, your server either exists on the internet, or it does not.

LINE, for example, explicitly requires webhook endpoints to be public and HTTPS-only (LINE Messaging API docs). OAuth providers follow similarly strict rules around redirect URIs (OAuth 2.0 RFC 6749).

The usual workflow looks like this:

  1. Start a local server.
  2. Expose it using a tunnel.
  3. Get a random public URL.
  4. Update the webhook or callback settings.
  5. Restart the server.
  6. Repeat everything, because the URL changed.

This works, but it is fragile. Everything breaks again the moment you restart your machine or your tunnel drops.

Why Random URLs Are Not Enough

Tools that expose a random public URL without authentication are convenient. But they do not scale beyond quick demos.

The problems show up fast:

  • Webhook URLs change on every restart.
  • OAuth redirect URIs need constant updates.
  • A stable endpoint is hard to share with teammates.
  • You cannot treat development like a real environment.

Some tools offer reserved domains as a paid feature (ngrok guide). This helps, but the underlying model still feels temporary rather than like real infrastructure.

For webhook-driven systems, instability is the real problem. You want your development environment to behave like production, only smaller and safer.

What Cloudflare Tunnel Actually Solves

Cloudflare Tunnel flips this model.

Instead of opening a port or exposing your machine directly, Cloudflare Tunnel creates an outbound tunnel from your local server to Cloudflare’s network. Cloudflare handles HTTPS, DNS, and routing (Cloudflare DNS). Your machine never accepts inbound traffic.

This gives you:

  • HTTPS by default (why HTTPS matters)
  • No public IP address required
  • No firewall or NAT configuration needed
  • Production-grade networking for local development

Cloudflared supports two distinct modes, and the difference matters.

cloudflare/cloudflaredGitHub

Cloudflare Tunnel client

★ 15,739 · ⑂ 1,446 · Go

Temporary Tunnels (No Login, No Domain)

You can run Cloudflared without logging in or owning a domain. It gives you a random HTTPS URL that forwards traffic to your local server (run a local tunnel).

This works fine for:

  • Quick testing
  • Demos
  • Debugging something once

This does not work well for:

  • Webhooks
  • OAuth
  • Long-running development
  • Anything you need to configure once and forget

Stable Tunnels (Login + Custom Domain)

This is where Cloudflare Tunnel becomes genuinely useful.

When you log in and attach a domain (create a tunnel), you get:

  • Stable subdomains
  • Persistent URLs
  • Multiple services behind one tunnel
  • Zero reconfiguration after restarts

Your local machine now behaves like a real environment.

A Real Use Case: LINE OA Chatbot Development

LINE Official Account webhooks are strict. They must be:

They do not support http://localhost:<port>.

With Cloudflare Tunnel, the flow becomes simple:

LINE Platform

https://api-dev.fasu.dev/webhook

Cloudflared Tunnel

http://localhost:8787

Your chatbot runs locally, LINE talks to a real HTTPS URL, and nothing breaks when you restart your server.

This setup pairs especially well with LINE LIFF, where a frontend LIFF app and a backend webhook often need to evolve together.

One Tunnel, Multiple Services

A single Cloudflare Tunnel can expose multiple local services under different subdomains. It does this with ingress rules (ingress configuration reference).

Example configuration:

tunnel: fasu-dev
credentials-file: ~/.cloudflared/credentials.json

ingress:
  - hostname: dev.fasu.dev
    service: http://localhost:3000

  - hostname: api-dev.fasu.dev
    service: http://localhost:8787

  - service: http_status:404

Now you have:

  • dev.fasu.dev serving your frontend
  • api-dev.fasu.dev serving your backend
  • Both services running locally
  • Both services using HTTPS
  • Both services staying stable

This setup mirrors how real environments are built. It treats development like a normal environment, not a special case.

Why This Beats the Usual Alternatives

This is not about declaring winners. It is about choosing tools that match the problem.

Cloudflare tunnels feel less like a workaround and more like real infrastructure:

  • URLs do not change.
  • Domains look like production domains.
  • Webhooks need no reconfiguration.
  • Development, staging, and preview environments can share the same pattern.

Once you set it up, it disappears into the background. This is exactly what infrastructure must do.

When Cloudflare Tunnel Is Overkill

Not every project needs this.

A temporary tunnel works fine if you are doing one of these things:

  • Sharing a quick demo
  • Testing something once
  • Debugging a one-off webhook

But if your workflow depends on webhooks, callbacks, or third-party integrations, stability is not optional.

Final Thoughts

The mistake I made early on was treating webhooks as a production-only concern.

They are not only a production concern.

If your development environment behaves differently from production, you will spend your time fighting tools instead of building features. This idea maps closely to dev-prod parity.

Once your local URLs stop changing, everything else gets easier.

← All writings