FeaturesGuidesBlogDocumentationPricingGet Started
Frameworks

Vite HMR Over an HTTPS Tunnel — Fix Hot Reload in 5 Minutes

Vite hot module reload breaks through tunnels because WebSocket and host checks expect localhost. Here is the config that fixes it.

The symptom

You run Vite locally. Hot module reload works on localhost:5173. You start a tunnel, open the HTTPS URL on your phone or share it with a teammate, and the page loads — but edits no longer hot-reload. Sometimes the browser console shows WebSocket connection failures or host validation errors.

The app works. HMR does not. This is a configuration mismatch, not a tunnel limitation.

The fix (start here)

Terminal 1 — Vite dev server:

npm run dev

Terminal 2 — tunnel:

npx localtolink --port 5173

Update vite.config.ts:

import { defineConfig } from "vite";

export default defineConfig({
  server: {
    hmr: {
      clientPort: 443,
      protocol: "wss",
    },
    allowedHosts: [".localtolink.com"],
  },
});

Open the tunnel HTTPS URL. Edit a component. HMR should reconnect through the secure WebSocket path.

Why HMR breaks without config

Vite's dev server serves JavaScript over HTTP and opens a WebSocket for hot updates. On plain localhost, the browser connects to ws://localhost:5173.

Through a tunnel, the browser loads the page from https://YOUR-SUB.localtolink.com. Vite's client tries to open a WebSocket to the wrong host/port unless you tell it:

LocalToLink forwards WebSocket traffic when the client connects correctly — the tunnel is not blocking HMR; the client was pointing at localhost.

Step-by-step verification

  1. Start Vite and confirm HMR on http://localhost:5173
  2. Apply the config above and restart Vite
  3. Start npx localtolink --port 5173
  4. Open the HTTPS tunnel URL in a browser tab
  5. Change a visible string in a component and save
  6. Watch the browser — update should apply without full page reload

If step 6 fails, open DevTools → Network → WS filter. You should see a WebSocket connection to your tunnel domain. Status 101 Switching Protocols means success.

allowedHosts in Vite 6+

Recent Vite versions validate the Host header against an allowlist. Tunnel domains are not localhost, so Vite may reject requests with:

Blocked request. This host is not allowed.

Adding .localtolink.com (leading dot allows subdomains) fixes browser access through the tunnel. If you use a different tunnel provider, substitute their domain pattern.

For local-only development without a tunnel, you can omit allowedHosts or include localhost.

Mobile testing through the tunnel

LocalToLink prints a QR code in the terminal. Scan it on your phone to load the Vite app over HTTPS — useful for responsive layout checks with real touch events.

HMR from your laptop still pushes updates to the phone browser as long as the phone tab stays open on the tunnel URL. Expect slightly higher latency than localhost; that is normal for any tunnel hop.

See test localhost on your phone for QR workflow details.

Common errors and fixes

SymptomFix
WebSocket failedSet hmr.clientPort: 443 and protocol: "wss"
Blocked hostAdd tunnel domain to allowedHosts
Full page reload instead of HMRCheck Vite version; ensure @vitejs/plugin-react is current
Works on desktop, not phonePhone must use HTTPS tunnel URL, not LAN IP
502 on tunnelVite not running or wrong --port

For broader tunnel diagnosis, see tunnel troubleshooting.

Docker + Vite + tunnel

If Vite runs in Docker:

docker run -p 5173:5173 -v $(pwd):/app -w /app node:22 npm run dev
npx localtolink --port 5173

The tunnel targets host port 5173. Vite config is the same. Ensure server.host: true or 0.0.0.0 inside the container so the port binding is reachable from the host.

Next.js note

Next.js Fast Refresh uses different configuration (allowedDevOrigins in Next.js 15.2+). If your stack is Next.js rather than Vite, see Next.js dev server through a tunnel instead of copying Vite settings.

Honest limits

LocalToLink free tier: 2-hour sessions, random subdomain, 1 GB bandwidth, 10 req/s. Sufficient for HMR development and mobile layout checks. Not for load testing or production hosting.

Restarting the tunnel assigns a new subdomain — update any bookmarked URLs. Brief disconnects within 10 minutes may reconnect to the same URL.

FAQ

Does every tunnel need this Vite config?
Any HTTPS tunnel that is not localhost requires HMR client settings pointing at WSS/443. The exact domain goes in allowedHosts.

Will HMR work for a client viewing my tunnel URL?
Yes, while your dev server and tunnel run. The client sees updates when you save files locally.

Can I disable HMR and use full reload?
Yes, but fixing config is usually faster. Set server.hmr: false only if you intentionally want manual refreshes.

Next steps

Try it now

Start a tunnel from your project directory — no account required.

$ npx localtolink

See the full CLI reference for flags and exit codes.

LocalToLink CLI 1.0.1 — verified 2026-09-05