vitejs/vite · error · Error

Port is already in use

Error message

Port ${port} is already in use

What it means

When binding the dev server's HTTP listener to a specific port, Vite iterates ports starting at server.port. If strictPort is true and that exact port is taken (EADDRINUSE), or if no port in the scan is free, it throws 'Port X is already in use'. For port 0 the same error covers the race between finding and binding an ephemeral port.

Solutions

  1. Free the port: find the PID (lsof -i :PORT) and kill it.
  2. Set strictPort: false so Vite picks the next free port automatically.
  3. Change server.port to a known-free value, or use port: 0 for ephemeral allocation.

Example fix

// before
server: { port: 5173, strictPort: true } // 5173 taken
// after
server: { port: 5173, strictPort: false }
Defensive patterns

Strategy: try-catch

Validate before calling

const net = require('net');
function isPortFree(port, host) {
  return new Promise((resolve) => {
    const tester = net.createServer()
      .once('error', () => resolve(false))
      .once('close', () => resolve(true))
      .listen(port, host, () => tester.close());
  });
}

Try / catch

try {
  await server.listen();
} catch (e) {
  if (/Port \d+ is already in use/.test(String(e.message))) {
    config.server.port = 0; // fall back to ephemeral
    await server.listen();
  } else throw e;
}

Prevention

When it happens

Trigger: Another process (often a previous Vite instance, a webpack-dev-server, or a system service) is already bound to the configured port. With strictPort: true Vite refuses to fall forward; without it, Vite scans upward and only errors if every port up to 65535 is busy.

Common situations: Hot-reload loops leaving zombie servers; running two dev tools on 3000/5173; Docker port-forward collisions; macOS AirPlay receiver occupying port 5000.

Related errors


AI-assisted analysis of vitejs/vite@b4d66fee14 (2026-08-11). Data as JSON: /api/errors/1b93d9782900a563. Report an issue: GitHub.

Appendix: source

Thrown at packages/vite/src/node/http.ts:274

  },
): Promise<number> {
  const { port: startPort, strictPort, host, logger } = serverOptions

  if (startPort === 0) {
    const port = await getAvailableEphemeralPort(host)
    if (port == null) {
      throw new Error('No available ephemeral port found')
    }

    const result = await tryBindServer(httpServer, port, host)
    if (result.success) {
      return port
    }
    if (result.error.code !== 'EADDRINUSE') {
      throw result.error
    }
    // this can happen if the port was listened by other process between getAvailableEphemeralPort and tryBindServer
    throw new Error(`Port ${port} is already in use`)
  }

  for (let port = startPort; port <= MAX_PORT; port++) {
    // Pre-check port availability on wildcard addresses (0.0.0.0, ::)
    // so that we avoid conflicts with other servers listening on all interfaces
    const portAvailableOnWildcard = await isPortAvailable(port)

    // If port is not available on a wildcard address but strictPort is set,
    // we still try binding directly before giving up.
    if (strictPort) {
      const result = await tryBindServer(httpServer, port, host)
      if (result.success) {
        if (!portAvailableOnWildcard) {
          logger.warn(
            colors.yellow(
              `Port ${port} is in use on a wildcard address, but ${host ?? 'localhost'}:${port} is available. ` +
                `There may be another server running on a wildcard IP on port ${port}.`,
            ),

View on GitHub (pinned to b4d66fee14)