How do I shut down a Node.js http(s) server immediately?
Asked Answered
S

12

74

I have a Node.js application that contains an http(s) server.

In a specific case, I need to shut down this server programmatically. What I am currently doing is calling its close() function, but this does not help, as it waits for any kept alive connections to finish first.

So, basically, this shuts down the server, but only after a minimum wait time of 120 seconds. But I want the server to shut down immediately - even if this means breaking up with currently handled requests.

What I can not do is a simple

process.exit();

as the server is only part of the application, and the rest of the application should remain running. What I am looking for is conceptually something such as server.destroy(); or something like that.

How could I achieve this?

PS: The keep-alive timeout for connections is usually required, hence it is not a viable option to decrease this time.

Stephanystephen answered 31/1, 2013 at 13:13 Comment(1)
There's an issue for this in node: github.com/nodejs/node/issues/2642Cruciferous
S
88

The trick is that you need to subscribe to the server's connection event which gives you the socket of the new connection. You need to remember this socket and later on, directly after having called server.close(), destroy that socket using socket.destroy().

Additionally, you need to listen to the socket's close event to remove it from the array if it leaves naturally because its keep-alive timeout does run out.

I have written a small sample application you can use to demonstrate this behavior:

// Create a new server on port 4000
var http = require('http');
var server = http.createServer(function (req, res) {
  res.end('Hello world!');
}).listen(4000);

// Maintain a hash of all connected sockets
var sockets = {}, nextSocketId = 0;
server.on('connection', function (socket) {
  // Add a newly connected socket
  var socketId = nextSocketId++;
  sockets[socketId] = socket;
  console.log('socket', socketId, 'opened');

  // Remove the socket when it closes
  socket.on('close', function () {
    console.log('socket', socketId, 'closed');
    delete sockets[socketId];
  });

  // Extend socket lifetime for demo purposes
  socket.setTimeout(4000);
});

// Count down from 10 seconds
(function countDown (counter) {
  console.log(counter);
  if (counter > 0)
    return setTimeout(countDown, 1000, counter - 1);

  // Close the server
  server.close(function () { console.log('Server closed!'); });
  // Destroy all open sockets
  for (var socketId in sockets) {
    console.log('socket', socketId, 'destroyed');
    sockets[socketId].destroy();
  }
})(10);

Basically, what it does is to start a new HTTP server, count from 10 to 0, and close the server after 10 seconds. If no connection has been established, the server shuts down immediately.

If a connection has been established and it is still open, it is destroyed. If it had already died naturally, only a message is printed out at that point in time.

Stephanystephen answered 31/1, 2013 at 22:52 Comment(4)
Should socket.on("close",...) be socket.once("close",...)?Charisecharisma
Or, as mentioned in a comment below, use github.com/hunterloftis/stoppable - it is likely to work better than the code above and will save you ink!Hirohito
The answer could be updated: sockets variable could use Set like const sockets = new Set(); sockets.add(socket); sockets.forEach((socket) => socket.destroy());.Biysk
If only I could give you a million upvotes. A little Express.js test thingy I've written was being really slow when it was loading pages that included external JavaScript and parsing them with JSDOM. I think JSDOM was doing some keep-alive shenanigans. But destroying the sockets as you suggested sorts it right out.Crosswise
G
24

I found a way to do this without having to keep track of the connections or having to force them closed. I'm not sure how reliable it is across Node versions or if there are any negative consequences to this but it seems to work perfectly fine for what I'm doing. The trick is to emit the "close" event using setImmediate right after calling the close method. This works like so:

server.close(callback);
setImmediate(function(){server.emit('close')});

At least for me, this ends up freeing the port so that I can start a new HTTP(S) service by the time the callback is called (which is pretty much instantly). Existing connections stay open. I'm using this to automatically restart the HTTPS service after renewing a Let's Encrypt certificate.

Graehme answered 24/4, 2016 at 23:27 Comment(4)
Based on your answer, I use server.close(); setImmediate(callback);.Accusal
Can you elaborate on what you're doing here, and why it works? It seems to me that calling server.close() and opening a new one in the callback will allow you to retain existing connections while listening for new. Your code example seems sketchy... you shouldn't have to emit events on the server like this, so I'm curious how you came to this conclusion.Albi
@Albi I've been upfront about the sketchiness of using this. It was simply an idea that I had at the time in an attempt to solve the issue that the callback wouldn't be called until all of the connections were closed (as evidenced by the question and other answers). Sure, I could have just gone the route of using setImmediate's callback directly as the comment above yours suggests, however that doesn't guarantee that no issues would be caused as some important cleanup actions might be dependent on the close event. Therefore I decided that just emitting the event isn't such a bad idea.Graehme
Excellent it works, and doesn't require tracking connections and having to make sure performance in production isn't affected.Cremator
C
14

If you need to keep the process alive after closing the server, then Golo Roden's solution is probably the best.

But if you're closing the server as part of a graceful shutdown of the process, you just need this:

var server = require('http').createServer(myFancyServerLogic);

server.on('connection', function (socket) {socket.unref();});
server.listen(80);

function myFancyServerLogic(req, res) {
    req.connection.ref();

    res.end('Hello World!', function () {
        req.connection.unref();
    });
}

Basically, the sockets that your server uses will only keep the process alive while they're actually serving a request. While they're just sitting there idly (because of a Keep-Alive connection), a call to server.close() will close the process, as long as there's nothing else keeping the process alive. If you need to do other things after the server closes, as part of your graceful shutdown, you can hook into process.on('beforeExit', callback) to finish your graceful shutdown procedures.

Crooked answered 19/7, 2015 at 19:5 Comment(2)
This actually looks like a good idea, but for some reason it's not working with Node v7.10.0 and Express 4.15.2. Have you tested it?Recognize
Worked for me right now with Node 14, but I’m not using any frameworks. This is brilliant, thanks!Goodtempered
A
8

The https://github.com/isaacs/server-destroy library provides an easy way to destroy() a server with the behavior desired in the question (by tracking opened connections and destroying each of them on server destroy, as described in other answers).

Alabama answered 13/1, 2015 at 18:37 Comment(2)
A maintained alternative, github.com/thedillonb/http-shutdown and github.com/hunterloftis/stoppable.Arron
I have since developed github.com/gajus/http-terminator. Unlike other libraries, http-terminator does not monkey-patch HTTP server instance.Arron
M
6

As others have said, the solution is to keep track of all open sockets and close them manually. My node package killable can do this for you. An example (using express, but you can call use killable on any http.server instance):

var killable = require('killable');

var app = require('express')();
var server;

app.route('/', function (req, res, next) {
  res.send('Server is going down NOW!');

  server.kill(function () {
    //the server is down when this is called. That won't take long.
  });
});

var server = app.listen(8080);
killable(server);
Melisandra answered 21/12, 2014 at 16:26 Comment(0)
P
3

Yet another nodejs package to perform a shutdown killing connections: http-shutdown, which seems reasonably maintained at the time of writing (Sept. 2016) and worked for me on NodeJS 6.x

From the documentation

Usage

There are currently two ways to use this library. The first is explicit wrapping of the Server object:

// Create the http server
var server = require('http').createServer(function(req, res) {
  res.end('Good job!');
});

// Wrap the server object with additional functionality.
// This should be done immediately after server construction, or before you start listening.
// Additional functionailiy needs to be added for http server events to properly shutdown.
server = require('http-shutdown')(server);

// Listen on a port and start taking requests.
server.listen(3000);

// Sometime later... shutdown the server.
server.shutdown(function() {
  console.log('Everything is cleanly shutdown.');
});

The second is implicitly adding prototype functionality to the Server object:

// .extend adds a .withShutdown prototype method to the Server object
require('http-shutdown').extend();

var server = require('http').createServer(function(req, res) {
  res.end('God job!');
}).withShutdown(); // <-- Easy to chain. Returns the Server object

// Sometime later, shutdown the server.
server.shutdown(function() {
  console.log('Everything is cleanly shutdown.');
});
Pagel answered 29/9, 2016 at 11:34 Comment(0)
D
2

My best guess would be to kill the connections manually (i.e. to forcibly close it's sockets).

Ideally, this should be done by digging into the server's internals and closing it's sockets by hand. Alternatively, one could run a shell-command that does the same (provided the server has proper privileges &c.)

Diesel answered 31/1, 2013 at 14:1 Comment(3)
The question is: How do I access the connections' sockets without using any undocumented behavior?Stephanystephen
Basically, your answer helped me get on the right track. See my answer on how I solved it (without using any undocumented behavior ;-)). Anyway, +1 for the right idea :-)Stephanystephen
That's why I was being vague. It depends rather lot what (if any) wrapper libraries you use (Express, for example) and what version of Node.js/libraries you're using (API's vary over time)Diesel
A
2

I have answered a variation of "how to terminate a HTTP server" many times on different support channels. Unfortunately, I couldn't recommend any of the existing libraries because they are lacking in one or another way. I have since put together a package that (I believe) is handling all the cases expected of graceful HTTP server termination.

https://github.com/gajus/http-terminator

The main benefit of http-terminator is that:

  • it does not monkey-patch Node.js API
  • it immediately destroys all sockets without an attached HTTP request
  • it allows graceful timeout to sockets with ongoing HTTP requests
  • it properly handles HTTPS connections
  • it informs connections using keep-alive that server is shutting down by setting a connection: close header
  • it does not terminate the Node.js process

Usage:

import http from 'http';
import {
  createHttpTerminator,
} from 'http-terminator';

const server = http.createServer();

const httpTerminator = createHttpTerminator({
  server,
});

await httpTerminator.terminate();

Arron answered 20/1, 2020 at 2:45 Comment(0)
E
2

Finally, we have server.closeAllConnections() function in v18.2.0

You can just call this function before server.close().

Also, if you don't want close active connections and want to wait for them, call server.closeIdleConnections() function before server.close().

Enrage answered 19/7, 2023 at 17:26 Comment(0)
Q
0

const Koa = require('koa')
const app = new Koa()

let keepAlive = true
app.use(async (ctx) => {
  let url = ctx.request.url

  // destroy socket
  if (keepAlive === false) {
    ctx.response.set('Connection', 'close')
  }
  switch (url) {
    case '/restart':
      ctx.body = 'success'
      process.send('restart')
      break;
    default:
      ctx.body = 'world-----' + Date.now()
  }
})
const server = app.listen(9011)

process.on('message', (data, sendHandle) => {
  if (data == 'stop') {
    keepAlive = false
    server.close();
  }
})
Quadruplicate answered 26/4, 2019 at 7:56 Comment(0)
P
0

I just did this with a Set

const connections = new Set<Socket>();
server.on('connection', (conn) => {
  connections.add(conn);
  conn.once('close', () => {
    connections.delete(conn);
  });
});


async function forceClose() {
  for (const conn of connections) {
    conn.destroy();
  }
  await new Promise((resolve) => {
    server.close(resolve);
  });
}
Powers answered 10/5 at 15:56 Comment(0)
D
-5

process.exit(code); // code 0 for success and 1 for fail

Doyledoyley answered 17/5, 2016 at 19:38 Comment(1)
The question says that process.exit() is not an option.Flowerer

© 2022 - 2024 — McMap. All rights reserved.