child_process.fork()
Start a second Node.js process running a module, connected to the parent by an IPC channel.
child_process.fork(modulePath[, args][, options])- Added in Node.js v0.5.0
Parameters
| Parameter | Type | Description |
|---|---|---|
$modulePath | <string> | <URL> | The module to run in the child. May be a file: URL since v17.4.0. |
$argsoptional | <string[]>= [] | List of string arguments, appearing in the child's process.argv from index 2. |
$optionsoptional | <Object> | Fork options. |
$options.cwdoptional | <string> | <URL>= process.cwd() | Current working directory of the child process. |
$options.detachedoptional | <boolean>= false | Prepare the child process to run independently of its parent process. |
$options.envoptional | <Object>= process.env | Environment key-value pairs, replacing the child's environment entirely. |
$options.execPathoptional | <string>= process.execPath | Executable used to create the child process. |
$options.execArgvoptional | <string[]>= process.execArgv | List of string arguments passed to the executable, such as --inspect or --max-old-space-size. |
$options.serializationoptional | <string>= 'json' | Kind of serialization used for messages: 'json', or 'advanced' for structured clone. |
$options.signaloptional | <AbortSignal> | Allows closing the child process using an AbortSignal. |
$options.killSignaloptional | <string> | <integer>= 'SIGTERM' | Signal used when the process is killed by timeout or abort signal. |
$options.silentoptional | <boolean>= false | If true, the child's stdin, stdout and stderr are piped to the parent; otherwise they are inherited. |
$options.stdiooptional | <Array> | <string> | Stdio configuration, overriding silent. An array form must contain exactly one 'ipc' entry or an error is thrown. |
$options.timeoutoptional | <number>= undefined | Maximum milliseconds the process is allowed to run. |
$options.uidoptional | <number> | Sets the user identity of the process (see setuid(2)). |
$options.gidoptional | <number> | Sets the group identity of the process (see setgid(2)). |
$options.windowsVerbatimArgumentsoptional | <boolean>= false | No quoting or escaping of arguments is done on Windows. Ignored on Unix. |
Return value
<ChildProcess>A ChildProcess instance with an IPC channel already open, so subprocess.send() and the 'message' event are available.
Errors
Throws synchronously if the stdio array form is used without exactly one 'ipc' entry; a module that cannot be started is reported as an 'error' event.
Changelog
- PHP v17.4.0, v16.14.0modulePath can be a WHATWG URL object using the file: protocol.
- PHP v16.4.0, v14.18.0The cwd option can be a WHATWG URL object using the file: protocol.
- PHP v15.13.0, v14.18.0The timeout option was added.
- PHP v15.6.0, v14.17.0AbortSignal support was added via the signal option.
- PHP v13.2.0, v12.16.0The serialization option is supported, allowing structured-clone messages.
- PHP v8.0.0The stdio option can now be a string.
- PHP v6.4.0The stdio option is supported.
fork() is spawn() specialised for Node: it launches the same Node binary on the module you name and wires up an IPC channel, so subprocess.send() in the parent and process.send() in the child can pass messages both ways.
When to reach for it
Use it to move CPU-bound work — image processing, a big JSON transform, a crypto loop — off the event loop, or to run a task that must be able to crash without taking the server down. Each child is a real OS process with its own heap and its own V8, so an out-of-memory crash or a native segfault is contained.
What trips people up
- It is not POSIX
fork(2). Nothing is copied; the child starts cold and shares no memory or variables. - Messages are serialised. With the default
'json'mode,Dateobjects become strings,undefinedproperties vanish andMap,SetandBigIntfail. Setserialization: 'advanced'for structured clone. - The channel keeps the parent's event loop alive; the parent will not exit until the child is killed or disconnected.
- The child inherits
execArgv, so a parent started with--inspectmakes every child fight for the same debug port unless you override it.
For shared-memory parallelism inside one process, use worker_threads instead.
Example
const { fork } = require('node:child_process');
const fs = require('node:fs');
// Write the child module next to this script, then clean it up.
fs.writeFileSync('sum-worker.js', `
process.on('message', (msg) => {
process.send({ total: msg.numbers.reduce((a, b) => a + b, 0) });
process.disconnect();
});
`);
const child = fork('./sum-worker.js');
child.on('message', (msg) => console.log('total:', msg.total));
child.on('error', (err) => console.log('fork failed:', err.code));
child.on('close', (code) => {
console.log('exit code:', code);
fs.unlinkSync('sum-worker.js');
});
child.send({ numbers: [1, 2, 3, 4] });
// total: 10
// exit code: 0When to use it
- Move a CPU-heavy transform out of an HTTP server's event loop into a worker process.
- Run an untrusted or crash-prone job where a segfault must not kill the main process.
- Build a small pool of Node workers that receive jobs over IPC and send results back.
Frequently asked questions
What is the difference between fork and spawn in Node.js?
fork() is a special case of spawn() that always runs the current Node executable on a module path and adds an IPC channel, so the two processes can exchange messages with send() and 'message'. spawn() runs any command and gives you only streams.
Is child_process.fork the same as the Unix fork system call?
No. Node's fork() starts a brand-new Node process that runs the module from the beginning, sharing no memory, variables or open state with the parent. Nothing is copied from the parent's heap.
When should I use fork instead of worker_threads?
Use fork() when the work must be isolated, such as code that can crash, leak memory, or needs its own V8 heap limits and CLI flags. Use worker_threads when you want lower startup cost and shared memory through SharedArrayBuffer.
More examples
Why the default JSON serialization loses data
Under the default 'json' mode the Date arrives as a string, the Set is flattened to an empty object and the undefined key disappears entirely. With serialization: 'advanced' the structured clone algorithm preserves all three. The cost is that both ends must agree on the mode at fork time; it cannot be changed later.
// parent.js
const { fork } = require('node:child_process');
function roundTrip(serialization) {
const child = fork('./echo-worker.js', [], { serialization });
child.on('message', (msg) => {
console.log(serialization, '->', msg);
child.disconnect();
});
child.send({ when: new Date(0), tags: new Set(['a']), missing: undefined });
}
roundTrip('json');
roundTrip('advanced');
// echo-worker.js
// process.on('message', (msg) => process.send(msg));
// json -> { when: '1970-01-01T00:00:00.000Z', tags: {} }
// advanced -> { when: 1970-01-01T00:00:00.000Z, tags: Set(1) { 'a' }, missing: undefined }A minimal worker pool
Two children handle four jobs concurrently. Results are collected into a Map keyed by input and sorted before printing, because the order in which workers reply is not deterministic. Killing the workers at the end matters: an open IPC channel keeps the parent's event loop alive, so without it the program would never exit.
const { fork } = require('node:child_process');
const jobs = [10, 20, 30, 40];
const results = new Map();
const workers = [fork('./square-worker.js'), fork('./square-worker.js')];
let pending = jobs.length;
workers.forEach((worker, i) => {
worker.on('message', ({ input, output }) => {
results.set(input, output);
if (--pending === 0) {
// Sort so the printed order does not depend on which worker finished first.
console.log([...results.keys()].sort((a, b) => a - b).map((k) => results.get(k)));
workers.forEach((w) => w.kill());
}
});
worker.send(jobs[i * 2]);
worker.send(jobs[i * 2 + 1]);
});
// square-worker.js
// process.on('message', (input) => process.send({ input, output: input * input }));
// [ 100, 400, 900, 1600 ]Overriding execArgv so children are debuggable
execArgv replaces the flags the child's Node binary receives, which is the fix for the common 'address already in use' error when debugging a forked process. silent: true switches stdio from inherited to piped, so the child's output arrives as streams on the parent rather than going straight to the terminal.
const { fork } = require('node:child_process');
// A parent started with --inspect would pass that flag straight down and
// every child would collide on port 9229. Give each one its own port.
const child = fork('./job.js', [], {
execArgv: ['--inspect=9230', '--max-old-space-size=256'],
silent: true, // pipe stdout back instead of inheriting the terminal
});
child.stdout.setEncoding('utf8');
child.stdout.on('data', (chunk) => console.log('child said:', chunk.trim()));
child.stderr.on('data', () => {}); // inspector banner goes here
child.on('close', (code) => console.log('exit code:', code));
Discussion