localhost:5173 / dev-server-reachable
Reaching the Vite dev server from anything other than localhost
The Vite dev server binds to localhost only and rejects unknown hostnames, so a second device, a container or a tunnel needs two separate settings: server.host to bind wider, and server.allowedHosts to accept the name being used.
Two different gates
Most of the confusion here comes from treating this as one problem. It is two. The bind address decides which network interfaces the server listens on. The host check decides which Host headers it answers. A request can pass the first and fail the second, and the error messages look nothing alike.
- Not bound wide enough: the connection never arrives — timeout or connection refused.
- Bound, but the name is not allowed: the connection arrives and gets a plain-text rejection.
The defaults you are working against
Both defaults are deliberate. The dev server has your source files and your environment on it, and it is not meant to be exposed without a decision.
export default {
server: {
host: 'localhost', // default — this machine only
allowedHosts: [] // default — no extra hostnames
}
}Gate 1 — the bind address
With the default, the socket exists only on the loopback interface. Your phone, another laptop, and the host side of a container are all outside it. --host or server.host binds to every interface instead, and Vite then prints a Network address next to the Local one.
Gate 2 — the host check
Vite compares the Host header against server.allowedHosts. IP addresses and localhost pass without configuration; a name does not. This is why a tunnel with a generated hostname is refused on the first request even though everything else is correct, and why the rejection reads as a flat sentence rather than a stack trace.
Blocked request. This host is not allowed.Which case is yours
- Phone or second machine on the same network — bind wider, then use the Network address Vite prints.
- Vite runs in Docker and the host browser gets nothing — bind wider inside the container and map the port.
- Vite runs in WSL2 and Windows cannot reach it — usually forwarded automatically, until it is not.
- A tunnel or a custom hostname is refused — the bind is fine, the name has to be allowed.
- You need https on the address — a separate problem, and it changes the HMR socket with it.
What not to do
Setting allowedHosts to true switches the check off for every hostname, which is what most copied snippets do. On a laptop on an untrusted network, any name that resolves to your machine then reaches your dev server. Name the host you need instead — it is one string, and it stays correct.
Once it is reachable, HMR may not be
The page and the HMR WebSocket are two connections. The page can load over a tunnel or a container mapping while the socket still tries to reach a host that does not exist from the browser side. A page that renders over the network but never updates is this, not a build problem.
# faq
Questions
Why can my phone not open localhost:5173?
Because localhost on the phone is the phone. The dev server also has to bind beyond loopback, and you then use the Network address Vite prints, not localhost.
Is --host the same as allowedHosts?
No. --host decides which interfaces the server listens on. allowedHosts decides which hostnames it answers. Exposing over a named host needs both.
Is it safe to expose the dev server on my network?
On a network you control, for as long as you need it. The dev server serves your source and applies no authentication, so a café or conference network is a different question.
Why does it work over the IP but not over a hostname?
IP addresses and localhost pass the host check without configuration. A hostname has to be listed in server.allowedHosts.
# next
Related
# sources
Checked against the Vite documentation on 2026-09-11.