Running a Node.js Server as a Service Using Forever


There are a wide range of methods to set up a Node.js process as a service on Linux, with little consensus on a standard at this point. That's fine - Node.js is still a long way away from staid maturity and the era in a technology's development in which people settle on the two or three standard ways of solving a given problem.



 
 

Related Links:

Content

Cached Text (at the time of saving)


There are a wide range of methods to set up a Node.js process as a service on Linux, with little consensus on a standard at this point. That's fine - Node.js is still a long way away from staid maturity and the era in a technology's development in which people settle on the two or three standard ways of solving a given problem. Here, I'll outline one of my presently preferred ways of setting up a Node.js server as a service on Linux, using Forever, an init.d script and some tailoring of the server application itself. The chief advantage of this method is that it allows for a graceful shutdown rather than just killing the process.
For later arrivals: note that this was originally written back in the archaic days of Node.js 0.4.* versions. The world changes! Some fixes to the original post were provided in January 2012 by Ben Atkin, and much appreciated. Further updates were made in July 2012.
Update as of 2/10/2013: The scripts here are for RPM-based distributions like Fedora, and are also probably over-complex for what they set out to do (e.g. see the sed parsing of Forever output). You might take a look at a set of more recent and simpler scripts for Ubuntu that can be easily adapted to RPM-based Linux as well.
Install Node.js on Linux
When installing Node.js for a server application, the two things to bear in mind are that (a) you really don't want to run any process as root if you don't have to, and (b) you have to launch a process as root in order to bind to privileged ports like 80 and 443. Well, point (b) isn't strictly true, as there are other ways to do this, but launching as the root user and then downgrading the process permissions to run as another (non-privileged) user after the port is bound is an easy method that will just work across a broad range of Linux variants.
Note that these instructions were written for installation of Node.js on Fedora.
So when installing Node.js, I first create a user that will own the running server process and the data. I'll add it to the sudo group for the sake of convenience during development, but that's probably not a good idea on a production system:
useradd -m -d /home/node node usermod -a -G sudo node
Now switch to the newly created node user:
su - node
Now install node.js by following the standard instructions to build from source. You'll want to have the node.js source downloaded and unpacked (or checked out from Github) into /home/node/node and owned by the node user.
cd node export JOBS=2 mkdir ~/local ./configure --prefix=$HOME/local/node make make install
Lastly, you will need to add the node executables to the PATH environment variable and set the NODE_PATH such that Node.js can see modules in the main node_modules library folder. You should probably set this up for all users, which can be done as follows.
Create the file /etc/profile.d/node.sh containing the following lines:
export PATH=/home/node/local/node/bin:$PATH export NODE_PATH=/home/node/local/node/lib/node_modules
Depending on your setup, you may instead need to create the file /etc/profile.d/node.csh containing the following lines:
setenv PATH /home/node/local/node/bin:$PATH setenv NODE_PATH /home/node/local/node/lib/node_modules
From here you'll no doubt be putting in place various necessary packages for your project, and so forth. That should all happen as the node user, and at a minimum you will need Forever, which will need to be installed globally through NPM:
npm -g install forever
Tailor Your Node.js Application
Firstly, your Node.js server application will have to downgrade its own permissions after it binds to all needed privileged ports. Your code should expect to launch under ownership by root, and alter its own permissions to run under the node user. Here is a trivial HTTP server in Express as an example:
var express = require("express"); var server = express.createServer(); var serverPort = 80; var nodeUserGid = "node"; var nodeUserUid = "node"; server.listen(serverPort, function() { process.setgid(nodeUserGid); process.setuid(nodeUserUid); });
Secondly, your server will need a way to listen for notices telling it to prepare for shutdown and respond when ready to shutdown. One way of doing this is to set up an HTTP server on a port only open to localhost, much as follows:
var server = express.createServer(); server.listen(10080); server.get('/prepareForShutdown', function(req, res) { if( req.connection.remoteAddress == "127.0.0.1" || req.socket.remoteAddress == "127.0.0.1" // 0.4.7 oddity in https only || req.connection.socket.remoteAddress == "127.0.0.1" ) { managePreparationForShutdown(function() { // don't complete the connection until the preparation is done. res.statusCode = 200; res.end(); }); } else { res.statusCode = 500; res.end(); } }); var managePreparationForShutdown = function(callback) { // perform all the cleanup and other operations needed prior to shutdown, // but do not actually shutdown. Call the callback function only when // these operations are actually complete. };
Thirdly, you will need to include a script with your application that will trigger the shutdown preparation. A simple method like the one above could be triggered via curl, but you may include more security measures in its use, or otherwise prefer to use Node.js:
#!/usr/bin/env node /* * Tell the server to gracefully prepare for shutdown, but do not end the process. */ console.log("Instructing server to prepare for shutdown"); var http = require("http"); var options = { host: "localhost", port: 10080, path: "/prepareForShutdown", method: "HEAD" }; var request = https.request(options, function(response) { console.log("Server completed preparations for shutdown"); }); request.end(); request.on("error", function(error) { throw error; });
Finally, your application should probably log to stdout (e.g. using console.log), as it will be running under a monitor that will redirect stdout to a log file. You can manage your own logging if you want, but why do extra work when it isn't necessary?
Set up an init.d Script for Forever
It is easy enough to use Forever to manage a Node.js server as a service via an init.d script. The trick is the graceful shutdown, as forever.js simply kills processes when used in this way. The following script and setup instructions are good for a Red Hat, Fedora, or similar Linux distribution, though you will have to change the paths to suit your application and installation details. For example it assumes that your application is packaged with scripts in /scripts/start.js and /scripts/prepareForStop.js to start the server and prepare for shutdown respectively.
#!/bin/bash # # Service script for a Node.js application running under Forever. # # This is suitable for Fedora, Red Hat, CentOS and similar distributions. # It will not work on Ubuntu or other Debian-style distributions! # # There is some perhaps unnecessary complexity going on in the relationship between # Forever and the server process. See: https://github.com/indexzero/forever # # 1) Forever starts its own watchdog process, and keeps its own configuration data # in /var/run/forever. # # 2) If the process dies, Forever will restart it: if it fails but continues to run, # it won't be restarted. # # 3) If the process is stopped via this script, the pidfile is left in place; this # helps when issues happen with failed stop attempts. # # 4) Which means the check for running/not running is complex, and involves parsing # of the Forever list output. # # chkconfig: 345 80 20 # description: my application description # processname: my_application_name # pidfile: /var/run/my_application_name.pid # logfile: /var/log/my_application_name.log # # Source function library. . /etc/init.d/functions NAME=my_application_name SOURCE_DIR=/path/to/my/application/package/scripts SOURCE_FILE=start.js user=node pidfile=/var/run/$NAME.pid logfile=/var/log/$NAME.log forever_dir=/var/run/forever node=node forever=forever sed=sed export PATH=$PATH:/home/node/local/node/bin export NODE_PATH=$NODE_PATH:/home/node/local/node/lib/node_modules start() { echo "Starting $NAME node instance: " if [ "$foreverid" == "" ]; then # Create the log and pid files, making sure that # the target use has access to them touch $logfile chown $user $logfile touch $pidfile chown $user $pidfile # Launch the application daemon --user=root \ $forever start -p $forever_dir --pidfile $pidfile -l $logfile \ -a -d $SOURCE_DIR $SOURCE_FILE RETVAL=$? else echo "Instance already running" RETVAL=0 fi } stop() { echo -n "Shutting down $NAME node instance : " if [ "$foreverid" != "" ]; then $node $SOURCE_DIR/prepareForStop.js $forever stop -p $forever_dir $id else echo "Instance is not running"; fi RETVAL=$? } if [ -f $pidfile ]; then read pid < $pidfile else pid = "" fi if [ "$pid" != "" ]; then # Gnarly sed usage to obtain the foreverid. sed1="/$pid\]/p" sed2="s/.*\[\([0-9]\+\)\].*\s$pid\].*/\1/g" foreverid=`$forever list -p $forever_dir | $sed -n $sed1 | $sed $sed2` else foreverid="" fi case "$1" in start) start ;; stop) stop ;; status) status -p ${pidfile} ;; *) echo "Usage: {start|stop|status}" exit 1 ;; esac exit $RETVAL
Copy your script into /etc/rc.d/init.d/my_application_name, and set its permissions appropriately. You can then set it to run as a service using a tool such as chkconfig:
/sbin/chkconfig --level 345 my_application_name on /etc/init.d/my_application_name start
Note that Forever acts as a process monitor: it will restart your Node.js server process if it stops for any reason. If you are using another process monitor (such as the ever popular Monit), you might want to disable that aspect of Forever. You can do that via the -m option:
# Launch the application daemon --user=root \ $forever start -p $forever_dir --pidfile $pidfile -m 0 -l $logfile \ -a -d $SOURCE_DIR $SOURCE_FILE