How to Run Multiple Node.JS Applications on Ubuntu 20.04 with Nginx

Updated on November 21, 2023
How to Run Multiple Node.JS Applications on Ubuntu 20.04 with Nginx header image

Introduction

While it is easy to launch a Node.js application in an Ubuntu terminal, at one point, every beginner asks: how to launch multiple applications at the same time? How do you keep them running when you close the terminal? Or, how do you connect your application to a domain name?

In this tutorial, we will be using several different tools:

  1. To launch your applications, you will install the NodeJS JavaScript runtime environment.
  2. To put your applications into production and run them in the background (as services), you will use PM2.
  3. To network your applications and attach them to domain names, you will install and configure Nginx.
  4. To secure your applications (HTTPS) and request Let's Encrypt security certificates, you will use Certbot.

Prerequisites

You need two things to complete this tutorial:

  • A server running Ubuntu 20.04. You should be logged in as a non-root user with sudo privileges.
  • If you want to connect your application to a domain name, you will need to have purchased a domain name and have DNS access. Have an A field open in your DNS with the following configuration: Hostname: yourDomainName.com / Type: A / TTL: 3600 / Data: IP address of your server

Installing Node.js with NVM

The first step is to install NodeJS. Then, to make it more flexible, we use the Node Version Manager to change the version of NodeJS at any time in the future.

Install Node Version Manager to your user account with the following command.

$ curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.38.0/install.sh | bash

To access the nvm script, restart your terminal or reload it with this command:

$ source ~/.bashrc

You can see all the versions of Node.js available with this command:

$ nvm list-remote

...
v14.12.0
v14.13.0
v14.13.1
v14.14.0
v14.15.0   (LTS: Fermium)
v14.15.1   (LTS: Fermium)
v14.15.2   (LTS: Fermium)
v14.15.3   (LTS: Fermium)
v14.15.4   (LTS: Fermium)
v14.15.5   (LTS: Fermium)
v14.16.0   (LTS: Fermium)
v14.16.1   (LTS: Fermium)
v14.17.0   (LTS: Fermium)
v14.17.1   (LTS: Fermium)
v14.17.2   (LTS: Fermium)
v14.17.3   (LTS: Fermium)
v14.17.4   (LTS: Fermium)
v14.17.5   (LTS: Fermium)
v14.17.6   (LTS: Fermium)
v14.18.0   (Latest LTS: Fermium)
v15.0.0
v15.0.1
v15.1.0
v15.2.0
v15.2.1
v15.3.0
...

You can install any of the versions of Node JS from this list. In this tutorial, we will install the latest recommended version of NodeJS, 14.18.0 (Fermium). Depending on when you read this tutorial, the next recommended version (Gallium) may be released. You can install it.

Now, install the Latest LTS version:

$ nvm install v14.18.0

To be sure that Node.js is properly installed, run this command:

$ node -v

v14.18.0

NodeJS is installed with its package manager, named NPM. To make sure it was installed correctly, use the following command:

$ npm -v

8.1.0

PM2 and Node.js projects

Once Node.js is installed, you can move on to the second step: Installing PM2! PM2 allows you to run multiple applications in the background. To install the manager, run the following command:

$ npm install pm2@latest -g

The -g flag allows you to install PM2 globally (to your user account).

Once installed, you define what your needs are depending on the type of your JavaScript project. There are generally two ways to launch a NodeJS application:

  1. Run the application using a main javascript file (usually at the root of your project)
  2. Or, run your application using a launch script found in the package.json file.

With main file

If your project has a main file (let's assume that it is called "main.js"), you can run your application from this file. But, before launching your application with PM2, I advise you to start your application in the terminal to be sure that there are no errors. PM2 will not tell you if your application is failing on startup.

$ node main.js

If there are no errors, launch your application with pm2. Use the following command to launch it:

$  pm2 start main.js

[PM2] Applying action restartProcessId on app [main](ids: [ 0 ])
[PM2] [main](0) ✓
[PM2] Process successfully started
...

To stop your application, use the following command:

$ pm2 stop main.js

If you are not in the file folder, you can stop the application with the name of the file:

$ pm2 stop main

To restart your application, run one of these commands:

$ pm2 restart main.js
or
$ pm2 restart main

With package.json

If you don't have a launch file like "main.js", you can launch your application with the scripts in package.json.

In the following example, we'll use the package.json file from a Next.js project. For the rest of the tutorial, the application will be started on port 3000.

...
"scripts": {
    "dev": "cross-env NODE_ENV=development PORT=3000 node index.js",
    "build": "next build",
    "start": "node index.js",
    "postinstall": "next build"
},
...

In this example, the application will be launched in a production environment ("start" script). But your app might have other values to use.

Now, create and edit a file named "ecosystem.config.cjs" at the root of your project:

$ sudo nano ./ecosystem.config.cjs

Once opened, write this in your file:

module.exports = {
  apps: [
    {
      name: 'myapp',
      script: 'npm',
      args: 'run start',
    },
  ],
};

The "name" field contains the name you want to give to your application. Replace "myapp" with the name you want to give it.

In the "args" field, you specify the launch script for your application. Replace "start" in "run start" with the script you want to run in the package.json file of your project (examples: "dev", "build" , "start", "postinstall, ...").

Once you have configured your file, start your application with the following command:

$ pm2 start ecosystem.config.cjs

After the first start, you can start your application with the name that you have assigned to it in "ecosystem.config.cjs".

$ pm2 start myapp

To stop it:

$ pm2 stop myapp

To restart it:

$ pm2 restart myapp

You can repeat this step for as many applications as you want to launch.

To know the status of applications managed by PM2, you can use the following command:

$ pm2 status

Nginx and Reverse Proxy

Now that your application is launched on your machine, install and configure Nginx to create HTTP access.

Your app runs and listens on localhost. When a request is made from outside the server, using a domain name, it is made to port 80 of your machine (port of the Nginx web server). But the application runs on port 3000. Nginx has a Proxy role here. That is, it redirects requests to the service (Node.js application) assigned to the configured domain name (in your DNS). You need to configure Nginx to tell it that your domain name corresponds to a service running on a specific port of your machine.

To install Nginx on your Ubuntu server, run the following commands:

$ sudo apt update
$ sudo apt install nginx

Before configuring Nginx, make sure your firewall is enabled and configured. First, turn on your firewall if you haven't already.

$ sudo ufw enable

Then allow Nginx in the firewall:

$ sudo ufw allow 'Nginx Full'
or
$ sudo ufw allow 'Nginx HTTP'
$ sudo ufw allow 'Nginx HTTPS'

Warning! If you are connected to your server via SSH and you have activated the firewall for the first time, you must add "OpenSSH" to the firewall.

$ sudo ufw allow 'OpenSSH'

Once the firewall is configured, create the Nginx configuration for your domain name.

$ sudo nano /etc/nginx/sites-available/example.com

Write this in the file

server {
  listen 80;
  listen [::]:80;
        
  server_name example.com www.example.com;
        
  location / {
    proxy_pass http://localhost:3000;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection 'upgrade';
    proxy_set_header Host $host;
    proxy_cache_bypass $http_upgrade;
  }
}

The "listen" fields indicate the listening port of Nginx. The first relates to IPV4 and the second to IPV6. Unless specifically configured, specify port 80.

The "server_name" field contains all the domain names that you attach to the configured application.

The "location" block indicates two things. The first, at "/", is the path of the URL that gives access to your application. In this example, it is "http://example.com/" which gives access to the application indicated in our configuration file.

Next, inside the "location" block is a "proxy_pass" field. The proxy_pass field is the local link of the application http://localhost:[PORT]. Remember, the application is running under port 3000, so the link is "http://localhost:3000".

You could have, with the same domain name, specified a more complete path for another application, in a second block (in the same file) or in another file. You could have indicated "/admin" for an application running on port 5000, such as a CMS. This would have resulted in having a root URL "http://example.com/" giving access to your main application and another URL "http://example.com/admin" giving access to a management application.

Optional:

server {
  listen 80;
  listen [::]:80;
        
  server_name example.com www.example.com;
        
  location /admin {
    proxy_pass http://localhost:5000;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection 'upgrade';
    proxy_set_header Host $host;
    proxy_cache_bypass $http_upgrade;
  }
}

When you have written the configuration that suits your application, you can check the syntax errors with the following command:

$ sudo nginx -t

Now, import your configuration into the /etc/nginx/sites-enabled folder with this command:

$ sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled

Finally, restart Nginx.

$ sudo service nginx restart
or
$ sudo systemctl restart nginx

If your DNS is configured correctly, you can access your application via HTTP, such as http://example.com.

Secure Nginx with Let's Encrypt

Currently, you can access your application over HTTP. But to secure the connection between the client and the server, you need to improve the protocol by changing it to HTTPS. To do this, install Certbot, a service that will generate and install Let's Encrypt SSL certificates for you.

To install Certbot on your machine, run the following commands:

$ sudo snap install --classic certbot

$ sudo ln -s /snap/bin/certbot /usr/bin/certbot

Once certbot is installed, request SSL certificates for your application:

$ sudo certbot --nginx
    
Which names would you like to activate HTTPS for?
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1: example.com
2: myapp.net
3: globalapp.org
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Now, select the domain name you want to secure ("example.com").

$ Select the appropriate numbers separated by commas or spaces, or leave input blank to select all options shown (Enter 'c' to cancel): 1

In this example, select the number 1 and press ENTER.

Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/example.com/fullchain.pem
Key is saved at:         /etc/letsencrypt/live/example.com/privkey.pem
This certificate expires on ...
These files will be updated when the certificate renews.
Certbot has set up a scheduled task to automatically renew this certificate in the background.
    
Deploying certificate
Successfully deployed certificate for example.com to /etc/nginx/sites-enabled/example.com
Your existing certificate has been successfully renewed, and the new certificate has been installed.
    
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
If you like Certbot, please consider supporting our work by:
* Donating to ISRG / Let's Encrypt:   https://letsencrypt.org/donate
* Donating to EFF:                    https://eff.org/donate-le
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Certbot modifies your Nginx configuration file automatically. You don't have to change it to enable HTTPS. Just be aware that certificates generated by Certbot have an expiration date. Certbot will most certainly have asked for your email address. You will receive an email alerting you of the expiration of your certificate a few weeks before the deadline. You only have to repeat the following command to regenerate and reinstall a certificate.

$ sudo certbot --nginx

Conclusion

In this tutorial, you have successfully installed Node.js on your machine, turned your applications into services running in the background with PM2, networked them and linked them to a domain name with Nginx, and finally, secure client-server communications by installing an SSL certificate.

You can deepen this tutorial by going to the official documentation of the technologies presented here: