Graceful shutdown
When your orchestrator deploys a new version, it sends your process a SIGTERM and then, after a grace period, a SIGKILL. Everything in between is your chance to finish in-flight work and shut down cleanly. A service that ignores SIGTERM will get killed mid-request, corrupt data, and make the deploy look flaky. A service that handles it will be boring, which is the goal.
import { Server } from 'http';
import { logger } from './logger';
import { client as dbClient } from './db';
export function installShutdown(server: Server) {
const shutdown = async (signal: string) => {
logger.info({ signal }, 'shutdown requested');
// Stop accepting new connections
server.close((err) => {
if (err) {
logger.error({ err }, 'error closing HTTP server');
process.exit(1);
}
});
// Give in-flight requests a bounded window to finish
setTimeout(() => {
logger.warn('shutdown timed out, forcing exit');
process.exit(1);
}, 10_000).unref();
// Close database pool
try {
await dbClient.end();
logger.info('database connection closed');
} catch (err) {
logger.error({ err }, 'error closing database');
}
process.exit(0);
};
process.on('SIGTERM', () => shutdown('SIGTERM'));
process.on('SIGINT', () => shutdown('SIGINT'));
}Stop accepting new connections, finish in-flight requests, close the database, exit.
Read the shutdown function top to bottom. Log that a shutdown was requested so it is visible in your logs. Stop accepting new connections. Give existing requests a bounded window to finish, usually ten seconds. Close connections to Postgres and Redis. Exit with 0 if everything cleaned up, 1 if something went wrong. SIGTERM and SIGINT both route through the same handler.
One subtle detail: setTimeout(...).unref(). Without unref, the pending timer keeps the event loop alive, which defeats the whole point of shutting down cleanly. unref tells Node to ignore that timer when deciding whether the process can exit. Small detail, saves a whole class of hanging-process bugs.
Validation checklist: Make your service observable
Loading practice…
Checkpoint: Observability checkpoint
Loading practice…