Run NodeJS with PM2 and Apache 2.4 on Ubuntu 18.04

NodeJS in production with PM2 and Apache

Overview

Running NodeJS in production is more than just running node against index.js. A single fault will take down your entire application, and no process will attempt to restart it. In this tutorial, you will learn how to properly run NodeJS in production with PM2 and Apache 2.4.

PM2 is a process manager written in Javascript, and its job is to ensure your NodeJS application is running. It also provides visibility into your application’s health, as well as maintains logs for it to ease troubleshooting.

To further improve your application’s availability and performance, Apache 2.4 can be layered on top. By using the web server as a reverse proxy we are able to better handle incoming traffic.

Reverse proxying also provides a solution to distribute load to multiple NodeJS backends, whether they are local to the Apache server, in containers, or on dedicated hosts.

Installing NodeJS and NPM

Installing NodeJS should be the first task. Your application needs to be executable, after all. The following instructions will show you how to install NodeJS on your server.

Installing NodeJS using PPA

NodeJS maintains a PPA for Ubuntu. We can install this PPA to more natively manage installations and patching on Ubuntu.

curl -sL https://deb.nodesource.com/setup_10.x -o nodesource_setup.sh
sudo bash nodesource_setup.sh
sudo apt install nodejs

For those who appreciate stability and improved security, the PPA is likely the best approach. However, there is a short coming to use PPA. The version available is the the long term support stable version, and you may need features added in more recent NodeJS releases.

Installing NodeJS using NVM

Node Version Manager (NVM) grants us a little more control over Node installations. It allows installing any version of NodeJS, not just the most recent LTS stable version.

To install Node on your servers using NVM, it must be installed. The following shows you how to download the installation script and run it.

curl -sL https://raw.githubusercontent.com/creationix/nvm/v0.33.11/install.sh -o install_nvm.sh
bash install_nvm.sh

The installation script run above does everything except add NVM to your current session’s PATH. Meaning, you cannot just simply call nvm yet.

You could log out of your user session and then log back in, but a better method is to run the source command. This will immediately apply the updated environment settings to your current session.

source ~/.profile

With NVM installed it is time to install NodeJS. The following command will install the latest version of NodeJS.

nvm install default

However, it is good practice to install the version of Node your application is written with. We can list the version of Node available for installation by running the nvm ls-remote command.

nvm ls-remote

The output of the command will look similar to the following. I’ve truncated the results to make it easier to see the different versions, however, the list of available NodeJS versions is rather long.

   v10.15.1   (LTS: Dubnium)
    v10.15.2   (LTS: Dubnium)
    v10.15.3   (LTS: Dubnium)
    v10.16.0   (Latest LTS: Dubnium)
     v11.0.0
     v11.1.0
     v11.2.0
     v11.3.0
     v11.4.0
     v11.5.0
     v11.6.0
     v11.7.0
     v11.8.0
     v11.9.0
    v11.10.0
    v11.10.1
    v11.11.0
    v11.12.0
    v11.13.0
    v11.14.0
    v11.15.0
     v12.0.0
     v12.1.0
     v12.2.0
     v12.3.0
     v12.3.1

To install any of the version listed, you specify the version with the NVM install command. For example, to install version 12.3.0 use the following command.

nvm install 12.3.1

Installing PM2

PM2 is written in Javascript and is available to be installed from the npm repository. You will install it globally to ensure the entire system has access to it.

sudo npm install -g pm2

Running a Node App with PM2

NodeJS applications can be started using the pm2 start command.

  1. Navigate to the directory where your NodeJS application is.
  2. Run the pm2 start command against it.
    pm2 start index.js
  3. Run the pm2 list command to view running NodeJS applications
    pm2 list

Stopping a NodeJS App in PM2

pm2 stop index

Giving a NodeJS App a Name in PM2

By default, the application name will be based on the application’s entry file name. For example, a application started with index.js will be named index. Custom names can be assigned to applications to make it easier to identity them.

pm2 start index.js --name "auth-api"

For more information on using PM2, visit the official website.

Before moving onto installing and configuring Apache, make sure your NodeJS application is running.

Installing Apache Web Server

Installing Apache Web Server is simple.

sudo apt install apache2

The Apache web server will be operating as a reverse proxy. Requests to it will be proxied to the backend NodeJS applications, managed by PM2. In order for Apache to proxy requests, the following modules must be installed and enabled.

sudo en2mod proxy
sudo a2enmod proxy_http

To fully enable the modules Apache must be restarted. Use the following command to restart Apache, if it is already running.

sudo systemctl restart apache2

Configuring Apache to Reverse Proxy to PM2

The simplest way to revere proxy requests using Apache is to use the ProxyPass and ProxyPassReverse directives. The following VirtualHost example shows how to proxy all “/” requests to a backend service, which is running on port 3000 of the local host.

<VirtualHost *:80>
    ProxyPreserveHost On

    ProxyPass / http://127.0.0.1:3000/
    ProxyPassReverse / http://127.0.0.1:3000/
</VirtualHost>

Configure Apache to Load Balance NodeJS Backend

As your service user base starts growing, you will want to look into distributing the traffic across multiple backends. The added value to this is increasing your service’s availability, as this removes single points of failure.

When deciding how to distribute the load to your NodeJS application, you must consider the available algorithms available in Apache. The simplest form of balancing is round-robin, which forwards each request to your backend in a sequential manner.

Not all traffic is equal and round-robin isn’t always a good use case. You may need better control to model how traffic is distributed, and Apache provides several additional algorithms, such by request and by traffic.

In addition to the other algorithms, weights can be applied to individual backend servers.

By only running a single instance of a NodeJS app you’ve created a single point of failure. Releasing a new update to the NodeJS microservice is likely to cause an outage.

To add high availability to your microserver, at least one more instance should run.

In order to balance against multiple NodeJS applications, a couple more modules must be enabled — proxy_balancer and lbmethod_byrequest.

sudo a2enmod proxy_balancer 
sudo a2enmod lbmethod_byrequests

proxy_balancer
Enables the ability to create a cluster of backend servers to balance against. A cluster created using the module is given a name, which is referenced by the ProxyPass andProxyPassReverse directives.

BalancerMember
Defines a backend service to include in a proxy_Balancer cluster.

<VirtualHost *:80>
    <Proxy balancer://microservice-cluster>
        BalancerMember http://127.0.0.1:3000
        BalancerMember http://127.0.0.1:3001
        BalancerMember http://127.0.0.1:3002
    </Proxy>

    ProxyPreserveHost On

    ProxyPass / balancer://microservice-cluster/
    ProxyPassReverse / balancer://microservice-cluster/
</VirtualHost>

Setting Apache Load Balancer Algorithms and Health Checks

A simple round-robin balance may work well, but it is usually more beneficial to set a different algorithm for balancing traffic. You should also ensure traffic is not proxied to a NodeJS service that is done, and therefore, cannot process the request.

Health checks are used to periodically validate the health of a backend service. Checks are simple requests sent to the NodeJS application.

sudo a2enmod lbmethod_bytraffic

Enabling the lbmethod_bytraffic module isn’t enough. You will need to restart Apache and then use ProxySet to set the balancing algorithm.

The following is an example Apache VirtualHost configuration setting the balancing algorithm.

<VirtualHost *:80>
    <Proxy balancer://microservice-cluster>
        BalancerMember http://127.0.0.1:3000
        BalancerMember http://127.0.0.1:3001
        BalancerMember http://127.0.0.1:3002
        ProxySet lbmethod-bytraffic
    </Proxy>

    ProxyPreserveHost On

    ProxyPass / balancer://microservice-cluster/
    ProxyPassReverse / balancer://microservice-cluster/
</VirtualHost>