Processes Best Practices
Process and worker boundaries protect security and stability - spawn safely, size pools honestly, and shut down gracefully under orchestrator signals.
How to Use This List
- Mandatory for any code using
child_processorshell: true. - Revisit when enabling cluster module or PM2 cluster mode.
- Count total DB connections as pool size × workers × pods.
- Pair with Graceful Shutdown runbooks.
A - child_process Security
- Prefer
spawn/execFilewith argument arrays overexecwith shell. Eliminates injection on user-influenced input. - Never use
shell: truewith interpolated user strings. Code review blocker. - Set
maxBufferconsciously on execFile or avoid buffered APIs for large output. Stream with spawn instead. - Reap child processes - always await
exitor handleerror. Avoid zombies and FD leaks. - Run children as least-privilege OS user in production. Not root unless absolutely required.
B - worker_threads and CPU
- Use fixed worker pools, not per-request Worker creation. Cap at CPU core count unless profiled otherwise.
- Keep I/O on main thread; CPU transforms in workers. DB and HTTP stay in primary isolate.
- Transfer ArrayBuffers when posting large binary to avoid clone cost. Use transfer list in
postMessage. - Handle
worker.on('error')and restart pool workers on failure. Prevent pool shrink to zero silently. - Do not assume native addons are thread-safe. Consult addon docs before worker use.
C - cluster and Scaling
- Avoid double-scaling cluster workers inside many K8s replicas. Pick orchestrator or cluster, rarely both at full scale.
- Use external store (Redis) for sessions and rate limits with multiple workers. No in-memory assumptions.
- Log
process.pidin structured logs. Debug which worker handled a request. - Refork workers with backoff on crash loops. Prevent CPU thrash on misconfiguration.
- Size connection pools:
poolMax × workers × replicas. Stay under database max connections.
D - Signals and Lifecycle
- Handle SIGTERM and SIGINT with idempotent shutdown function. Kubernetes uses SIGTERM.
- Call
server.close()and await DBpool.end()before exit. Close BullMQ workers too. - Set force-exit timer below platform grace period. Typically 25s on 30s K8s grace.
- Fail readiness probe when draining starts. Stop new traffic before shutdown work.
- Treat unhandledRejection as fatal in production APIs. Separate policy from graceful SIGTERM.
E - Architecture Choices
- Default to in-process async I/O for CRUD APIs. Processes/workers are escalation, not default.
- Use queues for durable background work, not fork per email. BullMQ over blind child_process.
- Document ADR when choosing cluster on bare metal vs pod-only scaling. Team alignment on ops model.
- Isolate untrusted code in OS sandbox/container, not just worker_thread. Workers share process privileges.
- Test shutdown in CI integration tests at least once per release train. Catch hang on keep-alive.
FAQs
Top security mistake?
exec(\git checkout $`)` with user-controlled branch - command injection.
How many worker_threads?
Start with min(4, cores - 1) and profile - workload dependent.
Is cluster obsolete?
Not on single VPS; uncommon inside K8s pods where replica scaling suffices.
PM2 vs manual signals?
PM2 adds reload and monitoring - still implement drain-friendly HTTP close in app code.
NestJS cluster?
Possible but ensure schedulers and cron do not run in every worker without leader election.
spawn shell for npm scripts?
spawn('npm', ['run', 'build']) without shell - cross-platform with documented npm path.
Windows process notes?
Test spawn paths on Windows CI if developers use mixed OS - document .cmd edge cases.
process.exit in libraries?
Libraries should not call process.exit - return errors to application shutdown orchestration.
Worker vs child for ffmpeg?
child_process spawn ffmpeg CLI - worker_threads for pure JS compute.
Connection math example?
pool 10 × cluster 8 workers × 3 pods = 240 DB connections - verify limit.
Signal testing locally?
kill -TERM <pid> while load testing with k6 or curl loop.
Further reading?
Related
- child_process - spawn vs exec
- Process Signals & Shutdown - SIGTERM patterns
- worker_threads - CPU pools
- Graceful Shutdown - operations
Stack versions: This page was written for Node.js 24.18.0 (Active LTS), npm 10+, TypeScript 5.6+, Express 5, Fastify 5, and NestJS 11.