/ Node

Add NodeJS to an existing Amazon EC2 LAMP server

This post is for people who already have a working LAMP stack on an Amazon EC2 Linux AMI but also want to host some sites on the same server using NodeJS without too much extra work.

I have an EC2 micro site using the Amazon Linux AMI that was already hosting a few Apache based websites but also wanted to be able to serve some NodeJS based sites, such as this Ghost blog. I also wanted to setup a proxy using HTTP-proxy to send incoming HTTP requests to the appropriate NodeJS or Apache server. Finally, I wanted to make sure the proxy would stay running on the background and restart automatically in case of crashes or changes to the proxy routes by using Forever and Nodemon.

Note: This guide assumes you're logged into your Amazon instance as the ec2-user account.


Steps

(1) Install some required compiler libraries and packages if they're not already installed.

sudo yum install gcc-c++ make

Install the openssl libraries:

sudo yum install openssl-devel

Install git to download source packages from Github.

sudo yum install git

(2) Clone the Node Version Manager (nvm) repository from https://github.com/creationix/nvm to install and manage NodeJS and NPM versions.

git clone git://github.com/creationix/nvm.git ~/.nvm

(3) To activate nvm, you need to source it from your bash shell first.

source ~/.nvm/nvm.sh

(4) Add nvm to ~/.profile file to have it automatically sourced upon login.

echo "source ~/.nvm/nvm.sh" >> ~/.bash_profile

(5) Install the latest stable NodeJS, as of this post it was v0.10.22.

nvm install v0.10.22

(6) Set v0.10.22 as the default version to be used. This will add ~/.nvm/v0.10.13/bin to the PATH environment variable.

nvm alias default v0.10.22

Check your PATH to make sure it appears correctly by typing:

echo $PATH

And it should return something like /home/ec2user/.nvm/v0.10.22/bin:/usr/local/bin

Also, check your NodeJS version with:

node -v

which should print v0.10.22


(7) At this point you've got NodeJS installed for use by your local user. You'll need something to proxy incoming connections to either Apache or NodeJS. You could use something like Nginx, but that seems like overkill in this case so instead I've chosen Nodejitsu's http-proxy package written for NodeJS and available from https://github.com/nodejitsu/node-http-proxy. This will proxy incoming connections to either an apache or NodeJS server according to the Domain Name used. You can run multiple NodeJS apps, each listening on their own port and proxied to based on the Domain Name being used, while Apache handles all other requests.

Note: you can also use another proxy like Bouncy from https://github.com/substack/bouncy, but we'll stick with HTTP-proxy for this tutorial as it's more robust, but YMMV


(8) We'll test the NodeJS installation so far by running a sample NodeJS application that listens to a port on your server and displays some text when your browser goes to that port on your server's IP addres.

You'll have to decide where you're going to store your NodeJS apps. In my case I created a new directory /var/www/node to store all my NodeJS files/directories, with /var/www/node/test to store the NodeJS test app. I keep my apache directories under /var/www/html so this makes all my websites easier to find.
Remember to update permissions appropriately on this directory so that the user which you will be running NodeJS with will have access.

Go to your newly created directory:

cd /var/www/node/

Use git to download a sample app to test NodeJS with from Heroku: https://github.com/heroku/node-js-sample

git clone https://github.com/heroku/node-js-sample.git

You'll need to install the Express web template libraries before you can run this sample nodejs project under NodeJS.

cd /var/www/node/node-js-sample
npm install express   

(Note: we're only installing this locally so we're not using the -g flag)

Now we can test NodeJS to make it's working correctly by running:

node web.js

You should see a message Listening on 5000 if NodeJS is working correctly. The NodeJS server is running the test site, but you won't be able to test it directly quite yet. This port is most likely blocked by your Amazon EC2 firewall and possibly also your server's firewall.

If you wish to test this port directly, login to your EC2 console, and edit your security group to allow access to port 5000. Also add a rule to your iptables config if you have that port currently blocked. Then go to <YOUR_IP>:5000 in your browser where <YOUR_IP> is the elastic ip address assigned to your EC2 instance.


(9) To allow your proxy to accept all incoming HTTP connections for proxying, we'll have to assign the HTTP-proxy app to port 80 and re-assign Apache to another port. For this tutorial let's assign Apache to listen on port 8000. Edit your apache server's .conf files and change all port listening commands from:

Listen 80 VirtualHost *:80

to

Listen 8000 VirtualHost *:8000

Restart your apache server:

sudo service httpd restart

If you wish to test this, open up port 8000 in your Amazon EC2 security group and go to <YOUR_IP>:8000 where <YOUR_IP> is the elastic IP address assigned to your EC2 instance. If you have a default Apache site setup, this should allow you to access it.


(10) Now that we can have both Apache and NodeJS running at the same time on different ports, it's time to tie them together via the reverse proxy to serve up websites appropriately from port 80. Ideally we don't want to run NodeJS as a root user to avoid any security complications. With linux, in order to listen to any port less than 1024 the user running the service must have root level access. One workaround is to have iptables redirect all packets arriving on port 80 to a higher port, which can then be listened to by a non-root user. We'll describe how to accomplish this later, but first let's get the proxy up and running.


(11) Now let's setup and configure HTTP-proxy. This example assumes port 8000 has been assigned as the listening port for Apache from the previous step. You can read up on HTTP-proxy here.

  1. Create a directory to store your proxy app.

  2. 'cd' to this directory and install the NodeJS http-proxy library files using:

    npm install http-proxy

  3. Create a js source file to define your proxy endpoints, for this example we'll use:

    emacs proxy.js

  4. Place the following text into proxy.js.(UPDATE: This is for http-proxy pre-1.0, as they have updated their api as of v1.0):

var http = require('http'), httpProxy = require('http-proxy');
var options = {
    router: {
        '.*': '127.0.0.1:8000',
        'example.nodejs.app':'9000'
    }
};
var proxyServer = httpProxy.createServer(options);
proxyServer.listen(8080);

The first entry under the router array will setup a default route to Apache using regular expressions to match all DNS, and assumes you have proper routes configured already in apache's conf files. The second entry in the routes array will route the DNS for 'example.nodejs.app' to port 9000, assuming this is the port you've configured a NodeJS app to run on. Of course, replace this with the FQDN you're planning on using with your node app. Read http://stackoverflow.com/questions/10930564/default-route-using-node-http-proxy for some more info on configuring the router table. Finally, the last line in the example code tells HTTP-proxy to listen to incoming connections on port 8080, which allows us to run it on the server as a non-root user.

At this point, you should have Apache listening on port 8000 and a NodeJS app listening on port 9000. Now we just need to redirect incoming web traffic on port 80 to port 8080, which is the port the HTTP-proxy is listening on.


(12) Configure iptables to redirect port 80 to a higher port. This allows you to run HTTP-proxy as non-root.

  1. First, run this command to see if you have ip forwarding enabled already:

    cat /proc/sys/net/ipv4/ip_forward

If it returns 0, then ip forwarding is disabled. A 1 means it's enabled. If it's disabled, edit the /etc/sysctl.conf file:

sudo emacs /etc/sysctl.conf     

In this file, make sure the following line appears:

net.ipv4.ip_forward = 1

This will enable ip forwarding. Then, to enable the changes made in sysctl.conf, run:

sudo sysctl -p /etc/sysctl.conf

Now, let's check that ip forwarding is enabled:

cat /proc/sys/net/ipv4/ip_forward

This should return a 1 now.

Now, let's set up forwarding with iptables from 80 to 8080 for use by HTTP-proxy:

sudo iptables -A PREROUTING -t nat -i eth0 -p tcp --dport 80 -j REDIRECT --to-port 8080

Finally, make sure your new rules are being saved on restart of machine or iptables daemon itself by doing the following steps. Run

sudo service iptables save

Now, check if rules are being saved. Restart iptables by running:

sudo service iptables restart

And then run:

sudo iptables-save 

You should see the prerouting rule we just added if everything is working properly.


(13) At this point we have Apache running and listening on port 8000, a sample NodeJS app running on port 9000, iptables routing port 80 to port 8080 and NodeJS running HTTP-proxy on port 8080 to proxy between NodeJS and Apache endpoints.

You can stop at this point, but if you want your HTTP-proxy app to restart in case NodeJS crashes (for non-code related reasons… if your code is broken, this won't magically fix it), you can also setup another daemon (or two) to do this automatically.


(14) In my case, I want to make sure HTTP-proxy continues to run in the background continuously. I also wanted it to restart automatically in case I changed the router configuration file when adding, removing or modifying proxy entries. Here's what I did to accomplish this:

  1. Install the Forever NodeJS utility. Forever is used to ensure a NodeJS app keeps running in case of crashes. Info on Forever can be found here: https://github.com/nodejitsu/forever. Run the following to install it and have it available for all your NodeJS apps:

    npm install -g forever

You can also use forever-monitor inside a NodeJS app to monitor changes in a script and perform actions without using the CLI version: https://github.com/nodejitsu/forever-monitor but we'll use the CLI version together with Nodemon, which is used to monitor file changes and restart a NodeJS automatically.

  1. Install the Nodemon utility. Nodemon reloads a NodeJS application whenever its source files are changed. Info here: https://github.com/remy/nodemon and http://nodemon.codeplex.com/. Run the following to install Nodemon globally:

    npm install -g nodemon

Note: You can also install and use Supervisor instead of Nodemon. Both are very similar in ease of use and features. Read up on this at: http://jaketrent.com/post/change-your-node-script-without-restarting/

  1. I want to use both Forever and Nodemon to ensure the NodeJS HTTP-proxy restarts if updated and is restarted if it dies. To do this I use the following command:

    forever /home/ec2-user/.nvm/v0.10.22/bin/nodemon --exitcrash proxy.js

If you modify the proxy.js from HTTP-proxy or kill the nodemon process, it will be restarted. You can test this yourself by editing the proxy.js file or using "kill" to kill the node process running the proxy.

  1. The final step to having a continuous proxy for your server is to make sure it is started at boot-time and ended on shutdown. I did this by adding this to my server's startup and shutdown flow.

First, create a script for use with the init.d system. Below is the script I used. I'm using the ec2-user with nvm to control versions, Nodemon to detect file changes and Forever to ensure the proxy stays up and running in case node crashes. I've places the following text into file with "sudo emacs /etc/init.d/nodejs-proxy" and did a "sudo chmod a+x /etc/init.d/nodejs-proxy".

		#!/bin/sh
		#
		# chkconfig: 35 99 99
		# description: Node.js /home/ec2-user/nodejs-proxy/proxy.js
		#

		. /etc/rc.d/init.d/functions
	
		USER="ec2-user"
		NODE_ENV="production"
		DAEMON="/home/ec2-user/.nvm/v0.10.22/bin/forever"
		ROOT_DIR="/home/ec2-user/nodejs-proxy"
		SERVER_NAME="nodejs-proxy"
		SERVER="/home/ec2-user/.nvm/v0.10.22/bin/nodemon --exitcrash"
		NODE_BIN="node"
		SERVER_FILE="/home/ec2-user/nodejs-proxy/proxy.js"
		LOG_FILE="$ROOT_DIR/proxy.log"
		LOCK_FILE="/var/lock/subsys/node-server"

		do_start()
		{
            if [ ! -f "$LOCK_FILE" ] ; then
                echo -n $"Starting $SERVER_NAME: "
                runuser -l "$USER" -c "NODE_ENV=$NODE_ENV $DAEMON $SERVER $SERVER_FILE>> $LOG_FILE &" && echo_success || echo_failure
                RETVAL=$?
                echo
                [ $RETVAL -eq 0 ] && touch $LOCK_FILE
            else
                echo "$SERVER is locked."
                RETVAL=1
            fi
	    }
		do_stop()
		{
	        echo -n $"Stopping $SERVER_NAME: "
	        pid=`ps -aefw | grep "$DAEMON $SERVER" | grep -v " grep " | awk '{print $2}'`
	        kill -9 $pid > /dev/null 2>&1 && echo_success || echo_failure
	        pid=`ps -aefw | grep "$SERVER $SERVER_FILE" | grep -v " grep " | awk '{print $2}'`
	        kill -9 $pid > /dev/null 2>&1 && echo_success || echo_failure
	        pid=`ps -aefw | grep "$NODE_BIN $SERVER_FILE" | grep -v " grep " | awk '{print $2}'`
	        kill -9 $pid > /dev/null 2>&1 && echo_success || echo_failure
	        RETVAL=$?
    	    echo
	        [ $RETVAL -eq 0 ] && rm -f $LOCK_FILE
		}
		case "$1" in
	        start)
	                do_start
	                ;;
	        stop)
	                do_stop
	                ;;
	        restart)
    	            do_stop
	        	    do_start
                	;;
    	    *)
        	        echo "Usage: $0 {start|stop|restart}"
	                RETVAL=1
		esac

		exit $RETVAL
  1. Now set this script to run on startup and shutdown by running the following commands:

       sudo chkconfig --add /etc/init.d/nodejs-proxy
       sudo chkconfig nodejs-proxy on
    
  2. Test your apache site and NodeJS site to make sure both are running.
    Restart your server and retest to make sure its working properly.

(15) And that's it, you should now have both NodeJS and Apache running on your server with a proxy to determine which one is used depending on the incoming FQDN. If you use OSX, and want to get setup to develop and test locally, check out this link: http://madebyhoundstooth.com/blog/install-node-with-homebrew-on-os-x/