Articles
Aug 8, 2023·2 min read

Graceful Shutdown in Node.js

What It Is

A "graceful shutdown" is when a system being turned off ensures the proper closure of processes and connections before stopping. The system understands how to shut itself down safely.

This is particularly useful when your app is shut down during an ongoing operation — such as an in-flight request. Your app should finish handling that operation before stopping.

Inter-Process Communication

To make this work, we use communication signals between operating system processes. You're already sending these signals when you use the kill command or press CTRL + C to terminate a process.

When CTRL + C is pressed, a signal is sent to the process. If that signal has a designated handler function, it will be called — otherwise, the default behavior is invoked (immediate termination).

Handling the Kill Signal

It's straightforward to create a custom handler for process termination. SIGTERM is the standard signal used to request graceful process termination.

const http = require('node:http');

const server = http.createServer((req, res) => {
  res.end();
});

server.listen(8000);

process.on('SIGTERM', () => {
  console.log('SIGTERM received.');
});

Now, when you run kill [PID], the log "SIGTERM received" will appear — but the process won't actually stop. That's because you caught the signal and then ignored it.

Closing the Server

Node.js provides a close event for this. The server.close() function stops the server from accepting new connections and calls the callback once all existing connections are closed.

const http = require('node:http');

const server = http.createServer((req, res) => {
  res.end();
});

server.listen(8000);

process.on('SIGTERM', () => {
  console.log('SIGTERM received.');
  console.log('Closing server...');

  server.close(() => {
    console.log('Server closed.');
  });
});

Node.js finishes execution when the event loop queue has no remaining tasks. However, if your application has open database connections or other async work, it may not terminate automatically. In that case, use process.exit explicitly:

const http = require('node:http');

const server = http.createServer((req, res) => {
  res.end();
});

server.listen(8000);

process.on('SIGTERM', () => {
  console.log('SIGTERM received.');
  console.log('Closing server...');

  server.close(() => {
    console.log('Server closed.');
    process.exit(0); // 0 = success, 1 = failure
  });
});

This pattern ensures your server finishes in-flight requests, closes connections cleanly, and exits with the correct status code — all before the process is killed.