Horizon

Share a Vite dev server

Share a Vite dev server through a Horizon tunnel to open it on a phone or send it to a teammate, with no allowedHosts change.

Open your Vite dev server from a phone or a teammate's laptop, with an HTTPS URL that reaches your machine.

Before you begin

  • Node.js 18 or later
  • A Horizon account and the CLI (see Getting started)
  • A reserved subdomain for -s. Reserve one on the Subdomains page.
  • A Vite project

Start your app

Vite listens on port 5173 by default and on localhost only. That is enough: the Horizon CLI runs on the same machine.

npm run dev

Start a tunnel

Use -s with a subdomain you reserved. Reserved subdomains are a paid feature, see Pricing.

hrzn tunnel http://localhost:5173 -s my-app
Output
HORIZON: Tunnel connected
  URL          https://my-app.hrzn.run (reserved)
  Forwarding   http://localhost:5173
  Request log  https://hrzn.run/dashboard/tunnels/my-app

Keep this terminal open.

Open the URL

Open https://my-app.hrzn.run in a browser. A first-time browser visitor sees the Before you continue page. Select Continue to site.

Check it works

The page loads and the Horizon terminal prints a line per request:

Output
  GET     200  /

Edit a source file and save. The page updates without a reload.

Why you don't need allowedHosts

Vite checks the Host header of every request. It allows localhost, domains under .localhost and all IP addresses by default. Horizon sets Host to localhost:5173, so Vite sees an allowed host. The public hostname arrives in X-Forwarded-Host, which Vite doesn't check.

Tested against Vite 8.3.4: a request with Host: my-app.hrzn.run got a 403, and a request with Host: localhost:5173 and Origin: https://my-app.hrzn.run got a 200 for a module file.

Hot module replacement

Vite serves hot module replacement (HMR) over a WebSocket on the same port, and the tunnel passes WebSockets through. If the browser console shows a failed WebSocket connection, set the client side of that connection with server.ws. Vite documents clientPort as the option to "override the port on the client side".

vite.config.ts
import { defineConfig } from "vite";

export default defineConfig({
  server: {
    ws: {
      protocol: "wss",
      clientPort: 443,
    },
  },
});

Add this only if HMR fails. Most setups don't need it.

Troubleshooting

Blocked request. This host ("my-app.hrzn.run") is not allowed.

Vite prints Blocked request. This host ("my-app.hrzn.run") is not allowed. To allow this host, add "my-app.hrzn.run" to server.allowedHosts in vite.config.js. Something sent your public hostname as Host. That means a proxy sits between the tunnel and Vite, or you tunneled to a URL that isn't localhost. Point the tunnel at http://localhost:5173. If you can't, add the hostname:

vite.config.ts
import { defineConfig } from "vite";

export default defineConfig({
  server: {
    allowedHosts: ["my-app.hrzn.run"],
  },
});

Vite warns that allowedHosts: true lets any website send requests to your dev server and download your source code. Use a list.

Port 5173 is in use, trying another one...

Vite moved to the next free port, so your tunnel points at the wrong one. Read the Local: line Vite prints and tunnel to that port, or free port 5173.

The URL changed after a restart

You started the tunnel without -s, so Horizon gave you a new random subdomain. Restart with -s my-app.

Next steps

On this page