This error occurs when a child process spawned by Node.js is terminated by receiving a SIGTERM signal before completing its task. SIGTERM is a termination signal sent by the operating system, container orchestrator, or parent process to request graceful shutdown. Understanding why the process receives SIGTERM and implementing proper signal handling prevents unexpected terminations and data loss.
When Node.js spawns a child process using child_process.spawn(), child_process.exec(), or similar APIs, the child process runs independently and can receive signals from the operating system or parent process. SIGTERM (signal 15) is a termination signal that requests the process to shut down gracefully, giving it time to clean up resources. When a child process is killed with SIGTERM, it means something external sent this signal before the process completed its work. This differs from SIGKILL (signal 9), which immediately terminates the process without cleanup opportunity. The error indicates the child process was terminated by external intervention rather than completing normally or crashing due to an internal error.
If you set a timeout option in child_process.spawn() or child_process.exec(), Node.js automatically sends SIGTERM when the timeout expires. Check your code and logs to see if processes are running longer than expected.
import { exec } from "child_process";
// PROBLEM - 5 second timeout may be too short
exec("long-running-command", { timeout: 5000 }, (error, stdout, stderr) => {
if (error) {
console.error(error); // Error: Command killed with signal SIGTERM
}
});
// SOLUTION - increase timeout or remove it
exec("long-running-command", { timeout: 30000 }, (error, stdout, stderr) => {
// Now has 30 seconds instead of 5
});The maxBuffer option (default 200KB) limits stdout/stderr buffer size. If exceeded, Node.js kills the child process with SIGTERM. Check if your command produces large output and increase maxBuffer accordingly.
import { exec } from "child_process";
// PROBLEM - default maxBuffer of 200KB may be too small
exec("command-with-large-output", (error, stdout) => {
// May fail with SIGTERM if output exceeds 200KB
});
// SOLUTION - increase maxBuffer to 10MB
exec("command-with-large-output", {
maxBuffer: 10 * 1024 * 1024 // 10MB
}, (error, stdout) => {
// Now supports larger output
});
// OR use spawn with streaming for very large output
import { spawn } from "child_process";
const child = spawn("command-with-large-output");
child.stdout.on("data", (data) => {
// Stream data instead of buffering all at once
process.stdout.write(data);
});If your child process is a Node.js script, implement a SIGTERM handler to clean up resources and exit gracefully. This prevents data loss and allows proper shutdown.
// In your child process script
process.on("SIGTERM", async () => {
console.log("Received SIGTERM, cleaning up...");
// Close database connections
await db.close();
// Finish writing files
await fs.promises.writeFile("state.json", JSON.stringify(state));
// Exit gracefully
process.exit(0);
});
// Also handle SIGINT for Ctrl+C
process.on("SIGINT", async () => {
console.log("Received SIGINT, cleaning up...");
process.exit(0);
});When you run "npm run start", npm starts as the main process and your Node.js app runs as a child. When npm receives SIGTERM, it immediately kills all children with exit code 143. In production and Docker, run node directly instead.
# PROBLEM - npm will not forward signals properly
CMD ["npm", "start"]
# SOLUTION - run node directly
CMD ["node", "dist/index.js"]
# OR use exec form to ensure node is PID 1
CMD ["node", "dist/index.js"]// In Procfile (Heroku/Railway)
// PROBLEM
web: npm start
// SOLUTION
web: node dist/index.jsIn Docker and bash scripts, use exec to replace the shell process with node, ensuring SIGTERM reaches the Node.js process directly instead of being blocked by an intermediate shell.
# PROBLEM - shell form creates intermediate process
CMD npm start
# SOLUTION - exec form makes node PID 1
CMD ["node", "dist/index.js"]#!/bin/bash
# PROBLEM - node runs as child of bash
node dist/index.js
# SOLUTION - exec replaces bash with node
exec node dist/index.jsContainer orchestrators like Docker and Kubernetes send SIGTERM then wait a grace period before sending SIGKILL. If your process needs more time to clean up, increase this grace period.
# Kubernetes deployment.yaml
apiVersion: apps/v1
kind: Deployment
spec:
template:
spec:
terminationGracePeriodSeconds: 60 # Default is 30
containers:
- name: app
image: myapp:latest# Docker - increase stop timeout from default 10s to 30s
docker stop --time 30 container_nameIdentify what is sending SIGTERM by checking logs, process hierarchy, and system events. This helps determine if it is timeout, container shutdown, or external signal.
# Check which process is the parent
ps aux | grep node
pstree -p | grep node
# Check system logs for signals sent
journalctl -u your-service.service | grep SIGTERM
# In Node.js, log when child processes exit
import { spawn } from "child_process";
const child = spawn("command");
child.on("exit", (code, signal) => {
if (signal === "SIGTERM") {
console.log("Child killed with SIGTERM");
console.log("Parent PID:", process.pid);
console.log("Child PID was:", child.pid);
}
});The distinction between SIGTERM and SIGKILL is critical for graceful shutdown: SIGTERM (15) can be caught and handled, allowing cleanup, while SIGKILL (9) immediately terminates the process without any cleanup opportunity. In containerized environments, the shutdown sequence is: send SIGTERM, wait grace period (default 30s in Kubernetes, 10s in Docker), then send SIGKILL if process still running. When debugging child process termination, note that exit code 143 specifically indicates SIGTERM (128 + 15), while exit code 137 indicates SIGKILL (128 + 9). Process managers like PM2 and systemd handle signals differently - PM2 forwards signals to the child process, while npm does not. The Node.js child_process timeout and maxBuffer options both use SIGTERM followed by SIGKILL if the process does not exit. For complex applications with multiple child processes, consider using a process supervisor like PM2 that properly manages signal propagation. In clustered environments, coordinate graceful shutdown across workers to avoid dropping connections or losing in-flight requests.
Error: Listener already called (once event already fired)
EventEmitter listener already called with once()
Error: EACCES: permission denied, open '/root/file.txt'
EACCES: permission denied
Error: Invalid encoding specified (stream encoding not supported)
How to fix Invalid encoding error in Node.js readable streams
Error: EINVAL: invalid argument, open
EINVAL: invalid argument, open
TypeError: readableLength must be a positive integer (stream config)
TypeError: readableLength must be a positive integer in Node.js streams