You've deployed your Node.js API. Everything is running smoothly. Then you need to restart the server.
You hit Ctrl+C or process.exit() — and suddenly:
Active requests are cut off
Database connections are left hanging
Users see errors
Some data never gets saved
Sound familiar? 😅
❓ What Is Graceful Shutdown?
Graceful Shutdown means when your server receives a termination signal, it:
✅ Completes all ongoing requests before closing
✅ Properly closes database connections
✅ Logs unfinished work
✅ Then exits cleanly
Ungraceful Shutdown is when you:
❌ Kill everything mid-process
❌ Leave database connections open
❌ Drop user requests without completing them
❌ Exit immediately without cleanup
🔥 Why Does It Matter in Production?
Data Loss
Imagine a payment is being processed, and the server shuts down midway — money deducted, but the order never completed.Database Connection Leaks
Open connections pile up over time. Eventually, the database says "No more connections!" and your entire app crashes.Unexpected Errors
When you restart, leftover connections or half-finished data cause weird, hard-to-debug issues.Poor User Experience
Users see errors during deployments or restarts. They lose trust in your application.
✅ How to Implement Graceful Shutdown — Step by Step
Here's a production-ready approach:
import { createServer } from "node:http";
import { logger } from "./logger.js";
import { connectDB, disconnectDB } from "./db.js";
const server = createServer(app);
const SHUTDOWN_TIMEOUT = 15000; // 15 seconds
let isShuttingDown = false;
async function shutdown(reason, exitCode = 0) {
// Prevent multiple shutdown attempts
if (isShuttingDown) return;
isShuttingDown = true;
logger.info({ reason, exitCode }, "Shutting down gracefully...");
// Force exit if it takes too long
const forceTimer = setTimeout(() => {
logger.error("Graceful shutdown timed out, forcing exit");
server.closeAllConnections();
process.exit(1);
}, SHUTDOWN_TIMEOUT);
forceTimer.unref();
try {
// 1️⃣ Stop accepting new requests
// (Already handled by server.close())
// 2️⃣ Wait for ongoing requests to finish
await new Promise((resolve, reject) => {
server.close((err) => (err ? reject(err) : resolve()));
});
logger.info("HTTP server closed");
// 3️⃣ Close database connections
await disconnectDB();
logger.info("Cleanup completed");
} catch (error) {
logger.error({ error }, "Error during shutdown");
process.exit(1);
}
}
// Handle termination signals
process.on("SIGTERM", () => shutdown("SIGTERM"));
process.on("SIGINT", () => shutdown("SIGINT"));
// Handle unexpected errors
process.on("uncaughtException", (err) => {
logger.fatal({ err }, "Uncaught exception");
shutdown("uncaughtException", 1);
});
process.on("unhandledRejection", (reason) => {
logger.error({ reason }, "Unhandled rejection");
shutdown("unhandledRejection", 1);
});
📝 Key Steps Explained:
Step What Happens
- Stop Accepting New Requests server.close() stops new connections but keeps existing ones alive
- Wait for Ongoing Requests Give active requests time to finish (5-10 seconds)
- Close Database Connections Properly close all DB connections to avoid leaks
- Log Everything Record the shutdown and any issues
- Exit Cleanly Only exit after all cleanup is done 🎯 Additional Production Tips: Set Proper Timeouts:
const server = createServer(app);
server.keepAliveTimeout = 65000; // Keep connections alive
server.headersTimeout = 70000; // Wait for headers
server.requestTimeout = 30000; // Complete requests
Add a Draining Period:
// Give the load balancer time to stop sending traffic
const DRAIN_DELAY = process.env.NODE_ENV === "production" ? 5000 : 0;
if (DRAIN_DELAY > 0) {
await delay(DRAIN_DELAY);
}
💡 Key Takeaways:
✅ Graceful shutdown prevents data loss and connection leaks
✅ It improves user experience during deployments
✅ It's essential for production-ready applications
✅ Implement it in every Node.js server you build
🚀 Summary:
Server shutdown is easy. Server shutdown done right is a skill.
A few lines of code can save your users from frustration and your database from crashing.
Do you implement Graceful Shutdown in your production servers? Or do you just hit Ctrl+C and hope for the best? 😅
Drop your thoughts in the comments! 👇
Top comments (0)