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 devStart a tunnel
Use -s with a subdomain you reserved. Reserved subdomains are a paid feature, see Pricing.
hrzn tunnel http://localhost:5173 -s my-appHORIZON: Tunnel connected
URL https://my-app.hrzn.run (reserved)
Forwarding http://localhost:5173
Request log https://hrzn.run/dashboard/tunnels/my-appKeep 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:
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".
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:
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
- Read Vite's server options.
Share a Next.js dev server
Share a Next.js dev server (next dev) through a Horizon tunnel, with the allowedDevOrigins setting that stops the blocked cross-origin warning.
Expose a local Express app
Expose a local Express app through a Horizon tunnel, and make req.hostname and req.protocol use the public URL with trust proxy.