Error fix · Developer errors

Node.js EPIPE: write EPIPE (broken pipe) Causes and Fixes

Fix Node.js "Error: write EPIPE". Understand broken pipes from closed sockets, piped output like head, and dead child processes, and handle them correctly.

Error messageError: write EPIPE

By SiteDownCheck Editorial · Updated November 4, 2026 · 4 min read

EPIPE means "broken pipe": your program wrote to a pipe or socket whose reading end is already closed. Nobody is listening, so the operating system refuses the write. In Node.js it appears as Error: write EPIPE with errno: -32 on Linux and macOS.

Error: write EPIPE
    at afterWriteDispatched (node:internal/stream_base_commons:160:15)
    at writeGeneric (node:internal/stream_base_commons:151:3) {
  errno: -32,
  code: 'EPIPE',
  syscall: 'write'
}

It is a sibling of ECONNRESET. See Node ECONNRESET for the read side. The big question for EPIPE is: what was the other end, and why did it go away?

Decision order

  1. Look at syscall and the stack. Is the write to a network socket, to a child process's stdin, or to process.stdout?
  2. Find out why the reader closed: it exited, it timed out, or it never wanted more data.
  3. Decide whether the error is harmless (client hung up) or a real fault (child crashed).

Case 1: writing to a network socket the peer has closed

A browser tab was closed, a client timed out, a load balancer gave up on a slow response, or the server you were uploading to rejected the request and closed early. When your code keeps writing a large body or streaming a response, it hits EPIPE.

Typical triggers:

  • Uploading a big file to a server that replies 413 or 401 and closes the connection before reading the whole body. See nginx 413.
  • Streaming a response to a client that disconnects.
  • A proxy or server idle timeout closing a connection while you were still preparing data.

Handle it on the stream, and stop work when the peer leaves:

res.on('close', () => { /* client left: cancel downstream work */ });
socket.on('error', (err) => {
  if (err.code === 'EPIPE' || err.code === 'ECONNRESET') return; // peer left
  throw err;
});

Use stream.pipeline() rather than manual pipe(). It propagates errors and destroys all streams, which prevents leaks and unhandled errors.

import { pipeline } from 'node:stream/promises';
try {
  await pipeline(fs.createReadStream(path), res);
} catch (err) {
  if (err.code !== 'ERR_STREAM_PREMATURE_CLOSE' && err.code !== 'EPIPE') throw err;
}

For uploads, read the response status early. If the server answers with an error before you finish, abort the upload instead of continuing to write.

Case 2: writing to a child process that exited

If you spawn a process and write to its stdin, but the process already exited or never reads stdin, you get EPIPE.

import { spawn } from 'node:child_process';
const child = spawn('gzip');
child.stdin.on('error', (e) => { if (e.code !== 'EPIPE') throw e; });
child.on('exit', (code, signal) => console.log({ code, signal }));

Check the exit code and stderr of the child. EPIPE is the symptom; the cause is whatever made the child quit, such as a missing binary argument, a crash, or a killed process. If the exit signal is SIGKILL, see Docker exit code 137 for memory kills.

Case 3: piping Node output into head or another short reader

This one is common and harmless:

node report.js | head -n 5

head exits after five lines, closes the pipe, and Node's next console.log fails with EPIPE. Options: ignore it for CLI tools by handling the stdout error, or accept the exit.

process.stdout.on('error', (err) => {
  if (err.code === 'EPIPE') process.exit(0);
  throw err;
});

Exiting quietly on EPIPE is conventional for command-line filters.

Case 4: stdout closed under process managers and terminals

A daemon whose parent terminal closed, or a process started over SSH that was disconnected, may lose stdout or stderr. Later console.log calls throw EPIPE and can take down the app. Send logs to a file or to journald, not to a terminal tied to your session. Under systemd, logs go to the journal: journalctl -u yourapp -f.

Case 5: database and cache connections

An idle database connection dropped by a firewall or server timeout yields write EPIPE or ECONNRESET on the next query. Use a pool that validates or recycles connections, set a pool idle timeout shorter than the infrastructure's, and enable TCP keep-alive. Test reachability of the database port with the Port Checker if the host is public.

What not to do

Do not globally swallow every uncaughtException to hide EPIPE. That leaves the process in an unknown state. Attach error listeners to the specific stream, handle EPIPE there, and let genuine bugs surface. Monitor the service so real failures reach you, using an uptime monitor on a health endpoint.

Next steps

FAQ

Does EPIPE mean my code is broken?

Not always. It often only means the other side stopped listening, such as a closed browser tab or head exiting. Treat it as an error only when the reader should still have been there.

Why does the process crash instead of just logging?

A stream error event with no listener is thrown as an uncaught exception. Add an error handler on the stream.

What does errno -32 mean?

It is the negative form of the Linux and macOS EPIPE code, 32. Windows builds report a different numeric value.

How is EPIPE different from ECONNRESET?

EPIPE happens when you write after the connection was closed cleanly. ECONNRESET happens when the peer aborted with a reset.

Get notified when this fix updates

Plus occasional practical tips on uptime and monitoring, nothing else. Unsubscribe anytime.

This page is general information written by our editorial team and updated when documentation changes. Servers and providers differ, so test changes safely first. See our editorial policy and disclaimer.