Why does SIGTERM handling not work correctly in NodeJS with NPM?

Resolution

In order to do proper signal handling in node, you'll want to not run as a child of npm. When you npm run foo, your node process is started as a child of npm, but npm won't handle signals like a normal process manager might. When we send the SIGTERM signal, we send it to the root-level process. If you're executing your app with npm run, that means npm actually receives the signals. What it decides to do with them afterwards is up to npm, and in the case of SIGTERM on Linux, that is to immediately kill any children (including your app) and to exit with a code of 143.

Instead, you can specify the startup scripts in your Procfile, like:

web: node index.js

That way, your app will be the top-level process and can handle signals as it likes.

Here's an example of an app using that pattern:

const http = require('http');

process
  .on('SIGTERM', shutdown('SIGTERM'))
  .on('SIGINT', shutdown('SIGINT'))
  .on('uncaughtException', shutdown('uncaughtException'));

setInterval(console.log.bind(console, 'tick'), 1000);
http.createServer((req, res) => res.end('hi'))
  .listen(process.env.PORT || 3000, () => console.log('Listening'));

function shutdown(signal) {
  return (err) => {
    console.log(`${ signal }...`);
    if (err) console.error(err.stack || err);
    setTimeout(() => {
      console.log('...waited 5s, exiting.');
      process.exit(err ? 1 : 0);
    }, 5000).unref();
  };
}
2017-01-05T21:08:08.896058+00:00 app[web.1]: tick
2017-01-05T21:08:09.594709+00:00 heroku[web.1]: Restarting
2017-01-05T21:08:09.595222+00:00 heroku[web.1]: State changed from up to starting
2017-01-05T21:08:09.897540+00:00 app[web.1]: tick
2017-01-05T21:08:10.291076+00:00 heroku[web.1]: Stopping all processes with SIGTERM
2017-01-05T21:08:10.298161+00:00 app[web.1]: SIGTERM...
2017-01-05T21:08:10.898861+00:00 app[web.1]: tick
2017-01-05T21:08:11.355244+00:00 heroku[web.1]: Starting process with command `node index.js`
2017-01-05T21:08:11.900103+00:00 app[web.1]: tick
2017-01-05T21:08:12.901466+00:00 app[web.1]: tick
2017-01-05T21:08:13.901688+00:00 app[web.1]: tick
2017-01-05T21:08:14.493141+00:00 app[web.1]: Listening
2017-01-05T21:08:14.902905+00:00 app[web.1]: tick
2017-01-05T21:08:15.204386+00:00 heroku[web.1]: State changed from starting to up
2017-01-05T21:08:15.362181+00:00 heroku[web.1]: Process exited with status 0
2017-01-05T21:08:15.481433+00:00 app[web.1]: tick
2017-01-05T21:08:15.298727+00:00 app[web.1]: ...waited 5s, exiting.
2017-01-05T21:08:16.483786+00:00 app[web.1]: tick