# Preview a dev server running on a remote machine

> Open a dev server that runs on a remote machine through a private SSH tunnel or a public link, and get past the Vite host check that blocks tunnels.

*2026-10-21 · previews, ssh, remote-dev*

Your agent ran `pnpm dev` on a remote machine, and the terminal says the app is at `http://localhost:5173`. That's the machine's localhost, not yours, so the link opens nothing on your laptop. There are two ways to bring it to your browser: a private SSH tunnel, or a public link.

## Private: SSH port forwarding

SSH can carry a port from the remote machine to your computer. The `-L` option in [ssh](https://man.openbsd.org/ssh.1) forwards a local port to a host and port on the remote side:

```bash
ssh -N -L 5173:localhost:5173 dev@203.0.113.10
```

Leave that running and open `http://localhost:5173` on your laptop. `-N` tells ssh not to run a remote command, since you only want the forward. Add `-f` to send it to the background.

Three things make this the default choice:

- **It's private.** Unless you change `GatewayPorts`, ssh binds the forwarded port to your loopback address ([ssh_config](https://man.openbsd.org/ssh_config.5)), so other devices on your network can't open it.
- **The dev server needs no changes.** Requests reach it as `localhost`, the host it expects.
- **Nothing new is exposed.** It rides on SSH access you already have.

The limit is that only you can see it, and only while the ssh command runs. To show a teammate or try the app on your phone, you need a link.

## Public: a quick tunnel

Cloudflare's [quick tunnels](https://developers.cloudflare.com/tunnel/get-started/quick-tunnels/) give a local port a temporary public address on `trycloudflare.com`, with no Cloudflare account or domain:

```bash
cloudflared tunnel --url http://localhost:5173
```

cloudflared prints an `https://` address. The docs are direct about the trade-offs:

- Anyone with the URL can reach your service until you stop cloudflared.
- The hostname changes every time you start a tunnel.
- Each tunnel handles up to 200 requests in flight; more get a `429`.
- Server-Sent Events don't work through them.
- They're meant for testing and development, and have no uptime guarantee.

If you run cloudflared yourself and want to limit who gets in, quick tunnels can also ask visitors for a one-time PIN sent to an allowed email address, with `--allowed-mail`.

## Why Vite says "Blocked request"

Open a Vite dev server through a tunnel and you'll probably see this instead of your app:

```text
Blocked request. This host ("some-random-words.trycloudflare.com") is not allowed.
To allow this host, add "some-random-words.trycloudflare.com" to `server.allowedHosts` in vite.config.js.
```

Vite checks the `Host` header of every request. Its [server options](https://vite.dev/config/server-options) accept `localhost`, names under `.localhost` and IP addresses by default. Through a tunnel, the request arrives carrying the tunnel's hostname, which isn't on that list, so Vite refuses it. The check is there to stop DNS rebinding, a trick that lets a website you visit point its own domain at your dev server and read from it.

You have two ways past it.

**Allow the tunnel's domain.** In `server.allowedHosts`, a name that starts with a dot allows that domain and every subdomain under it:

```text
// vite.config.js
export default {
  server: { allowedHosts: ['.trycloudflare.com'] },
}
```

Allow only the domains you use. Setting `allowedHosts` to `true` turns the check off for every host, and Vite's docs warn against it for the DNS rebinding reason above. If you serve a production build with `vite preview`, the matching option is `preview.allowedHosts`.

**Rewrite the Host header at the tunnel.** cloudflared can set the `Host` header on the requests it sends to your local service. Cloudflare documents it as the `httpHostHeader` [origin parameter](https://developers.cloudflare.com/cloudflare-one/networks/connectors/cloudflare-tunnel/configure-tunnels/origin-parameters/), and the command line takes it as `--http-host-header`:

```bash
cloudflared tunnel --url http://localhost:5173 --http-host-header localhost
```

Vite now sees `localhost` and serves the page without any change to its config. The visitor's browser still shows the tunnel's address.

## When a public link is fine

A preview link suits work you want someone to look at: a teammate reviewing a UI, a client checking a draft, your own phone on mobile data. It's the wrong tool when the app holds real data, has an admin page without sign-in, or talks to production services with real keys.

Keep in mind that a dev server sends your source code to the browser on purpose, so anyone with the link can read it. The random hostname makes a link hard to guess, but it stops being private once it's pasted into a chat. Stop the tunnel when the review is over, and use the SSH forward for anything that should stay private.

## How shedix does it

Each shedix machine page has a Ports panel that lists the ports listening on the machine. Pick one and it makes a public preview link for that port. The link is a Cloudflare quick tunnel that sends `Host: localhost` to your dev server, so Vite accepts it with its default settings. Anyone with the link can open it until you stop it or the machine stops.

For private access, use SSH port forwarding. Every machine takes SSH with the keys on your profile, so the `ssh -L` command above works as written. The machine's IP address changes each time it starts, so check the current one before you connect.

[Real Ubuntu](/use-cases/real-ubuntu) covers what's on a machine, the [security page](/security) explains what a preview link exposes, and the Ports calls are in [the API docs](/docs/api).
