How to Use Cloudflare Tunnel with Vite Local HTTPS Dev

How to Use Cloudflare Tunnel with Vite Local HTTPS Dev

Tako-kun ·

Sometimes .test is exactly right. You want a local HTTPS hostname, secure cookies, service workers, OAuth callbacks, and no port juggling, so you run tako dev and open https://my-app.test/.

Sometimes the app has to leave your laptop.

Maybe a webhook provider needs to call you back. Maybe a teammate needs to try a branch before you deploy it. Maybe an OAuth provider insists on a real public domain. You can solve that with Cloudflare Tunnel: cloudflared keeps an outbound connection to Cloudflare, and Cloudflare forwards a public hostname back to a service on your machine.

The trap is that Vite quite reasonably does not want to answer for every hostname on the internet. The tempting fix is server.allowedHosts = true, but the Vite docs call that out as unsafe because it opens the dev server to DNS rebinding attacks. The better fix is to make the tunnel hostname a real development route in Tako, then let tako.sh/vite add only that hostname to Vite’s allowed list.

The Shape We Want

The browser should hit a real public HTTPS URL. Cloudflare should tunnel that request to the local Tako dev proxy. Tako should route by Host header, terminate local HTTPS, and forward to Vite on a loopback port. Vite should accept the request because the hostname is one of the configured dev routes, not because every host is allowed.

Diagram

That last arrow is the whole point. The tunneled hostname is explicit application config, so the Vite dev server stays picky.

LayerHostname it seesWhat accepts it
Browserdev.example.comCloudflare’s public certificate and DNS route
cloudflareddev.example.comThe tunnel ingress rule
Tako dev proxydev.example.com[envs.development].routes in tako.toml
Vite dev serverdev.example.comtako.sh/vite adds the route host to server.allowedHosts
Local fallback URLmy-app.testTako-managed local DNS and HTTPS from tako dev

This is different from binding Vite to 0.0.0.0 or telling it to trust every host. Vite still listens on loopback. The public edge talks to Tako, not straight to Vite.

Configure Tako and Vite

Start with the Vite plugin. In vite.config.ts, add tako() alongside your framework plugins:

import { defineConfig } from "vite";
import { tako } from "tako.sh/vite";

export default defineConfig({
  plugins: [tako()],
});

During vite dev, the plugin adds .test, .tako.test, and the configured Tako dev route hostnames to Vite’s server.allowedHosts. Under tako dev, it also binds Vite to 127.0.0.1 and reports the chosen port back to the dev daemon, which is why Tako does not need to scrape Vite’s stdout for a URL.

Now give Tako the public hostname. Routes are host patterns, not URLs, so leave off https://:

name = "my-app"
preset = "vite"

[envs.development]
routes = ["my-app.test", "dev.example.com"]

The .test route is local and managed by Tako. The dev.example.com route is external: Tako will route it if traffic reaches the dev proxy, but it will not create DNS for it, advertise it in LAN mode, or rewrite it to .local. That split is intentional. LAN mode is for devices on your Wi-Fi; Cloudflare Tunnel is for traffic from the public internet.

You can also list only the external route:

[envs.development]
route = "dev.example.com"

When development routes contain no managed .test or .tako.test route, Tako keeps the default my-app.test route alongside the external hostname. We like the explicit two-route version in tutorials because it makes the shape visible, but the shorter version works.

Run the app:

tako dev

At this point https://my-app.test/ should work locally. If it does not, fix that first. The development docs, CLI reference, and tako.toml reference cover the local daemon, TLS trust, and route syntax.

Configure Cloudflare Tunnel

Use a named tunnel with a stable hostname. Quick tunnels are handy for experiments, but their random hostnames are awkward here because the hostname needs to be in tako.toml before tako dev starts.

Create the tunnel and publish the DNS route:

cloudflared tunnel login
cloudflared tunnel create tako-dev
cloudflared tunnel route dns tako-dev dev.example.com

Then create a local cloudflared config file. The important values are the service URL, the host header, and the local TLS settings:

tunnel: <tunnel-id>
credentials-file: /Users/you/.cloudflared/<tunnel-id>.json

ingress:
  - hostname: dev.example.com
    service: https://127.0.0.1:47831
    originRequest:
      httpHostHeader: dev.example.com
      matchSNItoHost: true
      noTLSVerify: true
  - service: http_status:404

Tako’s HTTPS dev daemon listens on 127.0.0.1:47831. On macOS and Linux, tako dev also arranges portless local URLs on :443, but the tunnel does not need that outer helper. It can talk directly to the daemon’s fixed HTTPS port.

httpHostHeader makes the request arrive at Tako as dev.example.com, which is what the route matcher needs. matchSNItoHost makes the TLS handshake use the incoming hostname as SNI; that lines up with Tako’s local certificate selection. noTLSVerify is there because the origin certificate is signed by your local Tako development CA, not by a CA Cloudflare already trusts. Keep that setting scoped to this local dev tunnel. For a real deployed origin, use normal TLS verification.

Cloudflare’s configuration file docs also show how ingress rules are matched and why the final catch-all rule is required. The origin parameters docs cover httpHostHeader, matchSNItoHost, and noTLSVerify in more detail.

Run the tunnel in another terminal:

cloudflared tunnel --config ~/.cloudflared/tako-dev.yml run tako-dev

Now open:

https://dev.example.com/

The public URL should hit the same app you see at https://my-app.test/, but without changing Vite to accept arbitrary hosts.

Debug the Hop That Fails

Tunnel setups are three small systems in a trench coat: Cloudflare DNS, cloudflared, and your local dev proxy. When something is off, the error usually tells you which hop to inspect.

SymptomLikely cause
Cloudflare 502tako dev is not running, or service points at the wrong local port
Tako 421 Misdirected Requestdev.example.com is missing from [envs.development], or the Host header is wrong
Vite “Blocked request”tako.sh/vite is missing, or the app was started outside tako dev
my-app.test works, tunnel does notCheck cloudflared tunnel ingress validate and the DNS route
Tunnel works, HMR is oddMake sure the tunnel proxies WebSocket traffic; Cloudflare Tunnel does for HTTP routes

For a direct local probe, this is useful:

curl -k -H "Host: dev.example.com" https://127.0.0.1:47831/

If that returns your app, Tako is configured correctly and the problem is on the Cloudflare side. If that returns 421, Tako is receiving the request but does not have the route. If Vite blocks it, the request made it all the way through the tunnel and proxy, but the app’s Vite config is missing the Tako plugin.

This setup is for development, not deployment. When you are ready to ship, use tako deploy and give the production environment its own public route. The neat part is that the local path and the production path share the same basic idea: routes are real config, the proxy routes by hostname, and the app does not need to know whether the request started three inches away or somewhere on the internet.