vercel/next.js · error

Server process exited before becoming ready (is port already

Error message

Server process exited before becoming ready (is port already in use?)

What it means

Thrown by waitForServerReady (benchmark.ts:308) when the spawned server child has already exited (exitCode !== null or signalCode !== null) before the readiness probe succeeds. This guard exists precisely because on a contested port a stale server can answer the probe while our child fails to bind — without this check the run would silently measure the wrong server. The message hints at EADDRINUSE because that's the most common cause of immediate child exit.

Source

Thrown at bench/render-pipeline/benchmark.ts:308

    throw new Error(
      `Missing ${NEXT_BIN}. Build Next.js first (pnpm --filter=next build).`
    )
  }
}

async function waitForServerReady(
  url: string,
  timeoutMs: number,
  serverDied?: () => boolean
): Promise<void> {
  const start = performance.now()
  while (performance.now() - start < timeoutMs) {
    // Without this check, a server that dies on startup (e.g. EADDRINUSE
    // against a stale server on the same port) is indistinguishable from
    // a slow one — worse, a 200 from whatever else owns the port would
    // pass, and the run would silently measure the wrong server.
    if (serverDied?.()) {
      throw new Error(
        `Server process exited before becoming ready (is port already in use?)`
      )
    }
    try {
      const response = await fetch(url, { cache: 'no-store' })
      await response.arrayBuffer()
      if (response.ok) return
    } catch {
      // server not ready yet
    }
    await sleep(200)
  }
  throw new Error(`Server did not become ready within ${timeoutMs}ms`)
}

function spawnedServerDied(server: ReturnType<typeof spawn>): () => boolean {
  return () => server.exitCode !== null || server.signalCode !== null
}

View on GitHub (pinned to 0ae8c72462)

Solutions

  1. Free the port: find and stop whatever holds it (the message's primary hint), then re-run.
  2. Pass a different --port to avoid the conflict entirely.
  3. For e2e, ensure `next build` ran successfully before `next start` (the benchmark builds by default; if you passed --build=false you must build manually).
  4. Reproduce the child's exit by running the same node command manually to see its stderr.

Example fix

# before — child exits on startup, port held by stale server
#   pnpm bench:render-pipeline --port=3199

# fix — free the port or pick another
# (find holder)
#   lsof -i :3199   # or: ss -ltnp 'sport = :3199'
# then either kill it or:
#   pnpm bench:render-pipeline --port=3200
Defensive patterns

Strategy: validation

Validate before calling

import { createConnection } from 'node:net'
async function isPortFree(port: number): Promise<boolean> {
  return new Promise((resolveFn) => {
    const sock = createConnection({ port, host: '127.0.0.1' })
    sock.on('connect', () => { sock.destroy(); resolveFn(false) }) // someone is listening
    sock.on('error', () => resolveFn(true))
  })
}
if (!(await isPortFree(port))) throw new Error(`port ${port} is in use`)

Type guard

function isServerDiedError(err: unknown): boolean {
  return err instanceof Error && /Server process exited before becoming ready/.test(err.message)
}

Try / catch

try {
  await waitForServerReady(url, timeoutMs, spawnedServerDied(server))
} catch (err) {
  if (isServerDiedError(err)) {
    console.error('Server child exited during boot. Reproduce with the exact `node` command to see stderr.')
    console.error('Common cause: EADDRINUSE — free the port or pass --port.')
  }
  throw err
}

Prevention

When it happens

Trigger: The spawned `next start` / minimal-server child dies during boot: EADDRINUSE (another process on --port), a startup exception, missing .next build output for `next start`, or a missing NODE/module. spawnedServerDied() flips true once exitCode/signalCode is set, and the next loop iteration throws.

Common situations: A previous benchmark/server left a process on the port (assertPortFree should catch this first, but timing windows and processes bound to 0.0.0.0 vs 127.0.0.1 can slip through); running `next start` without a prior `next build`; a runtime error in minimal-server.js; signal from OOM killer.

Related errors


AI-assisted analysis of vercel/next.js@0ae8c72462 (2026-08-06). Data as JSON: /api/errors/b2686723e38e7a1c. Report an issue: GitHub.