You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
When using browser mode, Vitest picks a port starting at 63315.
I've been observing failures with some users using "corporate" windows devices. I wasn't able to diagnose in detail but it seems that some security policies might prevent users from starting servers on high ranges.
IANA seems to recommend the following range 49152-65535 for ephemeral ports, hence the 63315, I guess.
Right now, I don't know if the whole range is blocked by such policies and if it would be more pragmatic to just use a port range <10000.
I am opening this issue to collect more feedback from the trenches.
Suggested solution
Pick ports in a lower range.
Alternative
The current workaround is to override the port manually in the configuration:
I don't actually know why the current port number was selected, but I don't think there is enough information here to justify changing the default that's been there for a few years now.
I hit this today and running netsh interface ipv4 show excludedportrange protocol=tcp showed me that the range 63272-63371 range was reserved by something.
Some update since this was opened. The config shape has changed. browser.api is deprecated in Vitest 5, and the port is now set with the top-level api option:
This seems to come up most often on Windows CI (for example facebook/lexical#8673 and voidzero-dev/vite-task#838). Windows reserves random blocks of the high port range and 63315 can land in one.
api: { port: 0 } (let the OS pick a free port) would be the natural option, but it's currently unsupported because Vitest treats 0 as unset and falls back to the default. Fixing that seems like a good first step.
Separately, we could reconsider the default. The browser mode port is mostly an internal connection between the Vitest server and the browser, so it doesn't need to be a fixed high port, at least when api/ui isn't requested.
The api: { port: 0 } case looks like a null-vs-zero check problem rather than
missing plumbing — on main the whole path treats 0 as "not set", so it never
reaches Vite.
Spots I found:
packages/vitest/src/node/config/resolveConfig.ts:105 — if (config.api.port)
(object form; 0 is dropped when api/ui already populated a default)
packages/vitest/src/node/config/resolveConfig.ts:120 — if (!api.port && !api.middlewareMode) api.port = defaultPort
This is the one that matters in browser mode: an explicit 0 gets overwritten
with the default passed in by the caller, so the listen call never sees it.
packages/vitest/src/node/config/resolveConfig.ts:132 — same !api.port check
again in the browser-forced block.
packages/vitest/src/node/plugins/browserLoader.ts:176 — if (config.api.port) await server.listen(config.api.port)
packages/vitest/src/node/logger.ts:245 — else if (this.ctx.config.api?.port && this.ctx.config._apiRequested)
(with 0 the "API started at ..." line would be skipped)
In browser mode that caller-side default is worth a look too: testConfig.ts:178
passes isBrowserEnabled ? harness._browserLastPort++ : defaultPort, and _browserLastPort starts at defaultBrowserPort (63315) in pluginHarness.ts:15. So each browser project takes the next number —
63315, 63316, ... — which is how a run can walk into a Windows reserved range
like the 63272-63371 reported above.
Vite itself already handles 0 end to end, which is why I think this is only the
checks above:
packages/vite/src/node/http.ts:260 — if (startPort === 0) { const port = await getAvailableEphemeralPort(host); ... return port }
packages/vite/src/node/server/index.ts:1182-1188 — the actual bound port is
stored on the server, and resolvedUrls is built from the real socket on listening.
Vitest already prefers the resolved URL over the configured one: logger.ts:249 and project.ts:495-496 use resolvedUrls?.local[0] ?? resolvedUrls?.network[0].
So my expectation is that != null (or ?? ) checks in those four/five places
are enough to let 0 flow into server.listen(0), with a test next to test/browser/specs/server-url.test.ts asserting that the resolved port is a
real ephemeral port and that the browser connects to it.
On the default number: the report above shows 63272-63371 reserved by Hyper-V on
one machine, which is exactly what swallowed 63315 — and since that reservation is
re-allocated on every boot, no fixed constant is safe. api: { port: 0 } looks
like the durable fix; I'd keep the current default unless you'd rather align
browser mode with the number you already use for the non-browser API server
(51204). Either way works for me.
If this direction looks right, could you add the approved label (or approve me)
so the PR doesn't get closed by the policy workflow? I'd keep the change to those
call sites plus one test.
Activity
sheremet-va commentedon Dec 22, 2025
I don't actually know why the current port number was selected, but I don't think there is enough information here to justify changing the default that's been there for a few years now.
birtles commentedon Apr 10, 2026
I hit this today and running
netsh interface ipv4 show excludedportrange protocol=tcpshowed me that the range63272-63371range was reserved by something.hi-ogawa commentedon Oct 14, 2026
Some update since this was opened. The config shape has changed.
browser.apiis deprecated in Vitest 5, and the port is now set with the top-levelapioption:This seems to come up most often on Windows CI (for example facebook/lexical#8673 and voidzero-dev/vite-task#838). Windows reserves random blocks of the high port range and 63315 can land in one.
api: { port: 0 }(let the OS pick a free port) would be the natural option, but it's currently unsupported because Vitest treats0as unset and falls back to the default. Fixing that seems like a good first step.Separately, we could reconsider the default. The browser mode port is mostly an internal connection between the Vitest server and the browser, so it doesn't need to be a fixed high port, at least when
api/uiisn't requested.Review note: https://gist.github.com/hi-ogawa/f7a22bbc9d0565270e84fbd060b640be
hi-ogawa commentedon Oct 16, 2026
We discussed and decided on:
api: { port: 0 }case.william-xue commentedon Oct 18, 2026
The
api: { port: 0 }case looks like a null-vs-zero check problem rather thanmissing plumbing — on
mainthe whole path treats0as "not set", so it neverreaches Vite.
Spots I found:
packages/vitest/src/node/config/resolveConfig.ts:105—if (config.api.port)(object form;
0is dropped whenapi/uialready populated a default)packages/vitest/src/node/config/resolveConfig.ts:120—if (!api.port && !api.middlewareMode) api.port = defaultPortThis is the one that matters in browser mode: an explicit
0gets overwrittenwith the default passed in by the caller, so the listen call never sees it.
packages/vitest/src/node/config/resolveConfig.ts:132— same!api.portcheckagain in the browser-forced block.
packages/vitest/src/node/plugins/browserLoader.ts:176—if (config.api.port) await server.listen(config.api.port)packages/vitest/src/node/logger.ts:245—else if (this.ctx.config.api?.port && this.ctx.config._apiRequested)(with
0the "API started at ..." line would be skipped)In browser mode that caller-side default is worth a look too:
testConfig.ts:178passes
isBrowserEnabled ? harness._browserLastPort++ : defaultPort, and_browserLastPortstarts atdefaultBrowserPort(63315) inpluginHarness.ts:15. So each browser project takes the next number —63315, 63316, ... — which is how a run can walk into a Windows reserved range
like the
63272-63371reported above.Vite itself already handles
0end to end, which is why I think this is only thechecks above:
packages/vite/src/node/http.ts:260—if (startPort === 0) { const port = await getAvailableEphemeralPort(host); ... return port }packages/vite/src/node/server/index.ts:1182-1188— the actual bound port isstored on the server, and
resolvedUrlsis built from the real socket onlistening.logger.ts:249andproject.ts:495-496useresolvedUrls?.local[0] ?? resolvedUrls?.network[0].So my expectation is that
!= null(or??) checks in those four/five placesare enough to let
0flow intoserver.listen(0), with a test next totest/browser/specs/server-url.test.tsasserting that the resolved port is areal ephemeral port and that the browser connects to it.
On the default number: the report above shows
63272-63371reserved by Hyper-V onone machine, which is exactly what swallowed 63315 — and since that reservation is
re-allocated on every boot, no fixed constant is safe.
api: { port: 0 }lookslike the durable fix; I'd keep the current default unless you'd rather align
browser mode with the number you already use for the non-browser API server
(51204). Either way works for me.
If this direction looks right, could you add the
approvedlabel (or approve me)so the PR doesn't get closed by the policy workflow? I'd keep the change to those
call sites plus one test.