Zero‑Downtime Reload with PM2: Architecture, Checks, and Failure Scenarios
Learn how PM2’s `reload` command achieves graceful restarts, the minimal architecture needed, data boundaries to avoid state loss, operational checks, and when to rethink the design.
14 Jun 2026, 03:22 UTC

Problem & Takeaway
When an HTTP/HTTPS microservice is under load, any restart can drop in‑flight requests. PM2’s reload command promises zero‑downtime by spawning a new worker, letting existing connections finish, and then terminating the old worker. To use this safely you need a small, well‑defined architecture, clear data boundaries, and operational checks that surface failures before traffic is affected.
Operational Requirements
- Node.js ≥ 12 (PM2 uses the
clustermodule andSIGUSR2). - Application must be clusterable: no synchronous startup that blocks the event loop for the new worker.
- All critical state must live outside the process (Redis, DB, file, etc.). In‑memory state is lost on reload.
- Properly close sockets and file descriptors during
preReload. - Load balancer or reverse proxy must support health‑check endpoints to route traffic only to ready workers.
Smallest Suitable Design
For a single microservice the minimal design is a one‑PM2‑process per service layout:
- Deploy the service with an
ecosystem.config.jsthat enables cluster mode but disableswatchandenvoverrides that could change between workers. - Define
preReloadandpostReloadhooks that gracefully drain connections and confirm readiness. - Expose a
/healthzendpoint that returns 200 only after the worker has finished its startup routine.
Example ecosystem.config.js snippet:
module.exports = {
apps: [{
name: 'api-service',
script: 'dist/index.js',
instances: 'max', // cluster mode
exec_mode: 'cluster',
env: {
NODE_ENV: 'production',
PORT: 3000
},
preReload: 'node scripts/flushCache.js',
postReload: 'node scripts/checkReady.js'
}]
};
In flushCache.js you would, for example, close any Redis pub/sub listeners or flush in‑memory caches that could become stale. checkReady.js could perform a quick DB ping to ensure the worker is fully functional before the load balancer starts routing traffic to it.
Trust and Data Boundaries
Because reload kills the old worker after spawning a new one, any data only held in process memory is lost. Design your service so that:
- Session data is stored in a shared store (Redis, Memcached).
- Message queues use durable brokers (Kafka, RabbitMQ).
- File uploads are written to a shared filesystem or object store before the worker exits.
- Stateful services expose an API to persist transient state to disk if needed.
When state cannot be externalized, reload is not suitable and you must fall back to restart or deploy a new instance behind a rolling update strategy.
Monitoring Hooks & Operational Checks
To verify that a reload is truly zero‑downtime, use the following checks:
- Log Validation
- Run
pm2 reload api-serviceon a staging environment. - Check
pm2 logs api-serviceforpreReloadandpostReloadentries. Errors here indicate a problem.
- Run
- Resource Snapshot
- Before reload:
pm2 monit api-service– note CPU, memory, and worker count. - During reload: confirm worker count rises to
N+1then falls back toN. - Look for spikes in CPU or memory that could indicate startup lag.
- Before reload:
- Socket Closure
- After reload, run
lsof -i :3000 | grep LISTENto ensure only the new worker is listening. - Use
netstat -anp | grep 3000to confirm no stray sockets remain.
- After reload, run
- Traffic Test
- Simulate a burst of HTTP requests (e.g., using
wrkorab) during the reload cycle. - Verify that
wrkreports no dropped connections or 5xx errors.
- Simulate a burst of HTTP requests (e.g., using
- Health‑Check Verification
- Ensure the load balancer only routes traffic to workers that return 200 from
/healthz. - After
postReload, runcurl -s -o /dev/null -w "%{http_code}" http://localhost:3000/healthzand expect200.
- Ensure the load balancer only routes traffic to workers that return 200 from
Failure Modes & When to Re‑Design
- In‑Memory State Loss
Any critical data kept in process memory (e.g., a cache of user profiles) will vanish. If your service cannot tolerate a brief state loss, consider adding a persistence layer or using a dedicated cache store.
- Synchronous Startup Tasks
Frameworks that block the event loop during startup (e.g., heavy ORM migrations) can cause the new worker to crash before it becomes ready. In such cases, disable
preReloador split the startup into asynchronous steps. - File Descriptor Leaks
If the application does not close sockets or file handles during
preReload, the old worker may keep descriptors open, eventually hitting the OS limit. Monitorlsofoutput and add cleanup logic. - Load Balancer Mis‑routing
If the LB does not honor the
/healthzendpoint, traffic may hit a worker that is still shutting down, leading to timeouts. Verify LB health‑check intervals and thresholds. - Cluster‑Unfriendly Code
Code that relies on
cluster.isMasterorcluster.forkinside the application can break the reload flow. Refactor to keep cluster logic in PM2 only.
When any of these failure modes surface in production, the minimal design should be replaced with a multi‑instance deployment that uses rolling updates or a blue‑green strategy, ensuring that each instance can be upgraded independently without shared state.
Practical Checklist
- Verify external state persistence.
- Confirm
preReloaddrains connections. - Ensure
postReloadsignals readiness via health‑check. - Run a reload in staging and perform the traffic test.
- Monitor
pm2 monitfor spikes. - Check
lsoffor descriptor leaks. - Validate LB routing logic.
Conclusion
PM2’s reload is a powerful tool for zero‑downtime deployments, but it requires a disciplined architecture: clusterable code, externalized state, and robust hooks. By following the operational checks above and being ready to shift to a rolling update when necessary, you can keep HTTP services online even during hot reloads.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.