server.listen()
Binds the server to a port, host, or Unix socket and starts accepting connections.
server.listen([port[, host[, backlog]]][, callback])- Added in Node.js v0.1.90
Parameters
| Parameter | Type | Description |
|---|---|---|
$portoptional | number | TCP port to bind. 0 asks the operating system for an unused ephemeral port. Omitted, it also means an ephemeral port. |
$hostoptional | string | Interface 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. |
$backlogoptional | number= 511 | Maximum length of the queue of pending connections. |
$callbackoptional | Function | Added 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 afterlisten()returnsnull. 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 calllisten(). - 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(), andclose()waits for keep-alive sockets to drain — useserver.closeAllConnections()to force it.
Example
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: numberWhen 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.
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.
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