Joe Doyle's Technical Blog

Getting started with Node.js and Nginx

I've started to move on to the next phase of learning about Node.js. I have a few sites created and for the most part IISNode has done a good job allowing me to run within IIS. Enabling output and kernel level caching gives a nice boost to performance as well. While this is all well and good, it's not how Node.js is generally run in production scenarios. I decided it was time to learn about hosting Node.js sites on Linux behind nginx.

The Goal

Here's what I want to accomplish.

  1. Get a Linux VM setup; Ubuntu 13.04 x64
  2. Install Node.js & nginx
  3. Configure nginx to proxy my site with caching enabled for static files
  4. Setup my site to start when the server boots

Installing Linux

There's not much exciting here. Just a vanilla Ubuntu server install. I made sure I had OpenSSH installed so I could manage it remotely. I've done this part before.

Important!
I am not an experienced Linux administrator. I can get around and do some basics, but Linux is undiscovered country for me. The steps below are what I've been able to scrape together off the internet. It worked for me. If there's something I did wrong or there's a better way, I'd love to hear about it!

Installing Node.js & nginx

Doing a little of the Google magic points out that while Ubuntu has a Node.js package, its not maintained or up to date. The Node repo has a nice Github wiki page covering the steps you need to do to add a reference to the up to date package.

sudo apt-get update  
sudo apt-get install python-software-properties python g++ make  
sudo add-apt-repository ppa:chris-lea/node.js  
sudo apt-get update  
sudo apt-get install nodejs  

This worked like a charm. Now I have Node v0.10.13 running.

I followed a similar process with nginx. They have straightforward documentation for each of the main Linux distros.

The first step is to install the nginx public key. I downloaded it the server then ran this command:

sudo apt-key add nginx_signing.key  

Next I added these two lines to the end of /etc/apt/sources.list

deb http://nginx.org/packages/ubuntu/ raring nginx  
deb-src http://nginx.org/packages/ubuntu/ raring nginx  

Now I'm ready to install.

apt-get update  
apt-get install nginx  

Success! nginx installed.

Configure nginx

This is where things got fun. So I found a good post on StackOverflow with an answer that looked like what I needed! So I started at the top and went to create a new file in /etc/nginx/sites-available. Only, I didn't have a sites-available directory. Did I miss a step?

Again, StackOverflow to the rescue! It turns out that the sites-available/sites-enabled setup is part of the Ubuntu maintained package, not the main package from the nginx folks. I like the concept of the sites-available/sites-enabled setup, so I decide to implement it. I create the directories, edit the /etc/nginx/nginx.conf file, restart nginx ( sudo service nginx restart), and now I can go back to getting the site setup.

I used an article from the ARG! Team Blog I found on Hardening Node.js For Production. Looked like what I wanted! Instead of putting the server configuration directly in the nginx.conf, I put mine in the sites-available directory and created a symbolic link to it in the sites-enabled directory. For those that want to see the command:

cd /etc/nginx/sites-enabled  
sudo ln -s /etc/nginx/sites-available/test.conf test.conf  

Here's the test.conf file:

upstream testsite {  
    server 127.0.0.1:3500;
}

server {  
    listen 80;
    access_log /var/log/nginx/test.log;

    location ~ ^/(images/|img/|javascript/|js/|css/|stylsheets/|favicon.ico) {
        root /home/joe/testsite/public;
        access_log off;
        expires max;
    }

    location / {
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
        proxy_set_header X-NginX-Proxy true;

        proxy_pass http://testsite;
        proxy_redirect off;
    }
}

The article from the ARG! Team Blog goes into detail about what's going on in this file. Here are the highlights:

Lines 1-3:
This defines where my Node.js site is at. In my case, its on the same machine on port 3500. This can be another server, or multiple servers to round-robin against.

Lines 9-13:
This defines where the static content is that nginx should serve instead of Node.js. Notice that it points to my public directory inside my site.

Lines 15-23:
This defines the root of the site that nginx should proxy for. We add a bunch of headers to tell Node.js/Express that there's a proxy in front of it.

Line 21:
The url here isn't the url used to access the site. Instead it is referring to Line 1 as the backend servers to send requests to.

Time to test it!

After I got all this setup, I started up my site. I opened it up in the browser and...


Welcome to nginx!

If you see this page, the nginx web server is successfully installed and working. Further configuration is required.

For online documentation and support please refer to nginx.org.
Commercial support is available at nginx.com.

Thank you for using nginx.


Not quite what I was expecting. At least I know nginx is running. But what went wrong? I rechecked everything and I thought it looked right. Then I remembered the instructions for enabling the sites-available/sites-enabled. I had added this line as directed:

include /etc/nginx/sites-enabled/*;  

What I missed was to remove the line that was already there:

include /etc/nginx/conf.d/*.conf;  

I commented it out by putting a # in front of it and restart nginx again. When I tested this time, success!

Here's my final nginx.conf after adding the rest of the parts from the ARG! Team blog:

user  nginx;  
worker_processes  4;

error_log  /var/log/nginx/error.log warn;  
pid        /var/run/nginx.pid;

events {  
    worker_connections  1024;
}

http {  
    proxy_cache_path  /var/cache/nginx levels=1:2 keys_zone=one:8m max_size=3000m inactive=600m;
    proxy_temp_path /var/tmp;
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    keepalive_timeout  65;

    gzip  on;
    gzip_comp_level 6;
    gzip_vary on;
    gzip_min_length 1000;
    gzip_proxied any;
    gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript;
    gzip_buffers 16 8k;

    #include /etc/nginx/conf.d/*.conf;
    include /etc/nginx/sites-enabled/*;
}

Ok, time for the last step.

Start the site when the server boots

I'm used to Windows services which are compiled programs. Ubuntu has Upstart which is a nicer script driven system. It looks like that's the modern approach for what I want to do.

Important
I'm not running a module to restart Node if it goes down! This is just a test. When I move a real production site behind nginx I will use a module like Forever.

I started with this StackOverflow post and couldn't get it to work. I did more searching and ran across the Upstart Cookbook which helped to explain what I was even trying to do, and then I found this post about Node.js and the Forever module. The example they gave was much simpler.

To create an Upstart script create a file in /etc/init. I called mine test.conf for simplicity. Here's what I ended up with in the file:

#!upstart

description "Test Node.js Site"

env FULL_PATH="/home/joe/testsite"  
env FILE_NAME="app.js"

start on startup  
stop on shutdown

script  
 exec node $FULL_PATH/$FILE_NAME > /home/joe/testsite/test.log
end script  

I start it up with:

sudo start test  

And the site is live!

I reboot the server and... the site is down. Hmm. Back to the Google.

This time it's AskUbuntu (a StackExchange Network Site) which has a perfectly named post: Why isn't my upstart service starting on system boot? It led me to try changing my start event from on startup on line 8 to:

start on net-device-up IFACE=eth0  

I reboot once again... and the site is up!

What next?

Now that I have a basic site setup I want to play around with moving a few other sites onto this server and off of IIS. Since I still do have sites that I want to keep on IIS, I'm also planning on having nginx proxy for those as well. If things go well I'll probably also move this site as well.