server.listen()

Binds the server to a port, host, or Unix socket and starts accepting connections.

Signature
server.listen([port[, host[, backlog]]][, callback])
  • Added in Node.js v0.1.90

Parameters

ParameterTypeDescription
$portoptionalnumberTCP port to bind. 0 asks the operating system for an unused ephemeral port. Omitted, it also means an ephemeral port.
$hostoptionalstringInterface to bind. Omitted, the server accepts on the unspecified IPv6 address (::) or IPv4 (0.0.0.0) — that is, all interfaces. Use '127.0.0.1' to keep it local-only.
$backlogoptionalnumber= 511Maximum length of the queue of pending connections.
$callbackoptionalFunctionAdded once to the 'listening' event. Receives no arguments.

Return value

<http.Server>The server itself, so the call can be chained. Binding is asynchronous — the server is not accepting connections until the 'listening' event fires.

Errors

Throws ERR_SERVER_ALREADY_LISTEN if called on a server that is already listening; bind failures such as EADDRINUSE and EACCES are emitted as an 'error' event instead of thrown.

Changelog

  • PHP v23.1.0, v22.12.0The options form supports reusePort, letting several processes bind the same port on Linux 3.9+ and modern BSDs.
  • PHP v15.6.0The options form accepts an AbortSignal, so aborting the signal closes the listening server.
  • PHP v11.4.0The options form supports ipv6Only, which disables dual-stack binding.

server.listen() is inherited from net.Server, so the same method covers HTTP, HTTPS, and raw TCP servers. It has four overloads: the port form above, an options object, a filesystem path for a Unix domain socket, and a pre-bound handle.

When to reach for it

Every server needs it exactly once. The two arguments that matter most in production are the ones people omit: passing '127.0.0.1' as host keeps a service off the public network, and passing 0 as port gets a free port from the kernel — the standard way to run tests in parallel without port collisions.

What trips people up

  • Binding is asynchronous. Reading server.address() on the line after listen() returns null. Read it inside the callback or after 'listening'.
  • EADDRINUSE is not thrown. It arrives as an 'error' event, and an unhandled 'error' event crashes the process. Attach a listener before you call listen().
  • Ports below 1024 need privileges on Unix; unprivileged processes get EACCES, not a permission message.
  • Listening keeps the event loop alive. A process that will not exit is usually one that never called server.close(), and close() waits for keep-alive sockets to drain — use server.closeAllConnections() to force it.

Example

Example · javascript
const http = require('node:http');

const server = http.createServer((req, res) => {
  res.writeHead(200, { 'Content-Type': 'text/plain' });
  res.end('ok\n');
});

// Attach the error handler BEFORE listening: an unhandled
// 'error' event on a server crashes the process.
server.on('error', (err) => {
  if (err.code === 'EADDRINUSE') {
    console.log('port 3000 is already taken');
  } else {
    throw err;
  }
});

// Port 0 asks the OS for any free port.
server.listen(0, '127.0.0.1', () => {
  const { address, port, family } = server.address();
  console.log(`bound: ${family} ${address}, port assigned: ${typeof port}`);
});
// bound: IPv4 127.0.0.1, port assigned: number

When to use it

  • Bind an ephemeral port with listen(0) so integration tests can run in parallel
  • Restrict a metrics or admin server to 127.0.0.1 so it is unreachable from outside the host
  • Listen on a Unix socket so nginx talks to the app without a TCP port
  • Run several worker processes on the same port with reusePort for kernel-level load balancing

Frequently asked questions

How do I fix EADDRINUSE in Node?

Either another process holds the port or a previous run of your own app has not exited. Find the holder with lsof -i :3000 (or ss -ltnp on Linux) and kill it, or bind a different port. In tests, use listen(0) so the OS picks a free port and the collision cannot happen.

How do I get the port a Node server is listening on?

Call server.address().port inside the listen callback or after the 'listening' event — it returns { address, family, port }. Before binding completes, server.address() returns null, which is why reading it on the line right after listen() gives an error about reading 'port' of null.

Why does my Node process not exit after I stop the server?

server.close() stops accepting new connections but waits for existing ones, and keep-alive sockets stay open until keepAliveTimeout expires. Call server.closeAllConnections() to destroy them immediately, or server.closeIdleConnections() to cut only the idle ones and let in-flight requests finish.

More examples

The options form, with an AbortSignal for shutdown

The options object is the only form that takes an AbortSignal, which makes graceful shutdown a one-liner instead of a close() callback dance. closeAllConnections() is the part people miss: without it, keep-alive sockets hold the event loop open for up to keepAliveTimeout after the server has stopped accepting.

Example · javascript
const http = require('node:http');

const controller = new AbortController();
const server = http.createServer((req, res) => res.end('ok'));

server.listen({
  port: 8080,
  host: '127.0.0.1',
  backlog: 511,
  ipv6Only: false,
  signal: controller.signal,
}, () => {
  console.log('listening');
});

process.on('SIGTERM', () => {
  // Stop accepting new connections...
  controller.abort();
  // ...and cut idle keep-alive sockets so the process can exit.
  server.closeAllConnections();
  console.log('shutting down');
});

Listening on a Unix domain socket

With a path instead of a port, server.address() returns the path string rather than an object. The rmSync before listening is required in practice: a Unix socket file outlives a crashed process and the next start fails with EADDRINUSE even though no process holds the socket.

Example · javascript
const http = require('node:http');
const fs = require('node:fs');

const SOCKET = './app.sock';

// A stale socket file from a crashed process causes EADDRINUSE.
fs.rmSync(SOCKET, { force: true });

const server = http.createServer((req, res) => {
  res.writeHead(200, { 'Content-Type': 'text/plain' });
  res.end('served over a unix socket\n');
});

server.listen(SOCKET, () => {
  console.log('listening on', server.address()); // './app.sock'
  fs.chmodSync(SOCKET, 0o660);
});

process.on('exit', () => fs.rmSync(SOCKET, { force: true }));

Discussion

  • Be the first to comment on this lesson.