How to Set Up a Node.js Application for Production on Ubuntu 20.04

introduce

Node.js is an open-source JavaScript runtime environment for building server-side and network applications. The platform runs on Linux, macOS, FreeBSD, and Windows. Although you can run Node.js applications from the command line, this tutorial will focus on running them as services. This means they will restart on reboot or failure, and are safe to use in production environments.

In this tutorial, you will set up a production-ready Node.js environment on an Ubuntu 20.04 server. This server will run a Node.js application managed by PM2 and provide users with secure access to the application through an Nginx reverse proxy. The Nginx server will serve HTTPS using a free certificate from Let’s Encrypt.

prerequisites

This guide assumes you have the following:

? Ubuntu 20.04 server setup as described in the Ubuntu 20.04 initial server setup guide. You should have a non-root user with sudo privileges and an active firewall.

?A domain name pointing to the server’s public IP. This tutorial will use the domain name example.com throughout.

? Nginx installed, as described in How To Install Nginx on Ubuntu 20.04.

? Configure Nginx for SSL with a Let’s Encrypt certificate. How To Secure Nginx With Let’s Encrypt On Ubuntu 20.04 will walk you through the process.

After completing the prerequisites, you will have a server serving your domain’s default placeholder page at https://example.com/.

Step 1 – Install Node.js

Let’s start by installing the latest LTS release of Node.js, using the NodeSource package archive.

First, install the NodeSource PPA in order to access its content. Make sure you are in your home directory and use curl to retrieve the installation script for the latest LTS version of Node.js from its archive.

cd ~
curl -sL https://deb.nodesource.com/setup_14.x -o nodesource_setup.sh

You can inspect the contents of this script using nano or your preferred text editor:

nano nodesource_setup.sh

After checking the script, run it under sudo:

sudobash nodesource_setup.sh

The PPA will be added to your configuration, and your local package cache will be automatically updated. After running the install script from Nodesource, the Node.js package can be installed:

sudo aptinstall nodejs

To check the version of Node.js installed after these initial steps, type:

node-v
Outputv14.4.0

Note: When installing from the NodeSource PPA, the Node.js executable is called nodejs, not Node.

The nodejs package contains the nodejs binary as well as npm, the package manager for node modules, so there is no need to install npm separately.

npm uses configuration files in the home directory to track updates. It will be created the first time you run npm. Execute this command to verify that npm is installed and create a configuration file:

npm -v
Output6.14.5

In order for some npm packages to work (for example, those that require compiling code from source), you will need to install the build-essential package:

sudo aptinstall build-essential

Now you have the necessary tools to work with npm packages that require compiling code from source.

With the Node.js runtime installed, let’s move on to writing Node.js applications.

Step 2 – Create Node.js Application

Let’s write a Hello World application that returns “Hello World” to any HTTP request. This sample application will help you set up Node.js. You can replace this with your own application, just make sure to modify the application to listen on the appropriate IP address and port.

First, let’s create a sample application called hello.js:

cd ~
nano hello.js

Insert the following code into the file:

~/hello.js

const http =require('http');const hostname ='localhost';const port =3000;const server = http.createServer((req, res)=>{
  res.statusCode =200;
  res.setHeader('Content-Type','text/plain');
  res.end('Hello World!\\
');});

server.listen(port, hostname,()=>{
  console.log(`Server running at http://${hostname}:${port}/`);});

Save the file and exit the editor.

This Node.js application listens on the specified address (localhost) and port (3000) and returns “Hello World!” with a 200 HTTP success code. Since we are listening on localhost, remote clients will not be able to connect to our application.

To test your application, type:

node hello.js

You will receive the following output:

OutputServer running at http://localhost:3000/

Note: Running a Node.js application in this way will block other commands until the application is terminated by pressing CTRL+C.

To test the application, open another terminal session on the server and connect to localhost using curl:

curl http://localhost:3000

If you get the following output, the application is working fine and listening on the correct address and port:

OutputHello World!

If you don’t get the expected output, make sure your Node.js application is running and configured to listen on the correct address and port.

Once you’re sure it’s working, press CTRL+C to kill the application (if you haven’t already).

Step 3 – Install PM2

Next, let’s install PM2, a process manager for Node.js applications. PM2 can daemonize applications so that they run in the background as services.

Install the latest version of PM2 on the server using npm:

sudonpminstall pm2@latest -g

The g option tells npm to install the module globally so that it is available system-wide.

Let’s first run your application hello.js in the background using the pm2-start command:

pm2 start hello.js

This also adds your application to PM2’s process list, which is output every time the application is started:

Output...
[PM2] Spawning PM2 daemon with pm2_home=/home/sammy/.pm2
[PM2] PM2 Successfully daemonized
[PM2] Starting /home/sammy/hello.js in fork_mode (1 instance)
[PM2] Done.
┌─────┬──────────────────────┬────────────┬───────────────────────── ────────┬──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ id │ name │ mode │ ? │ status │ cpu │ memory │
├─────┼─────────────────────┼────────────┼───────────────────────────────────────────────────────── ────────┼────────────────────────────────────────────────────────┤
│ 0 │ hello │ fork │ 0 │ online │ 0% │ 25.2mb │
└─────┴─────────────────────┴───────────┴──────────────────────────────────────────────────────────── ───────┴────────────┴────────────┘

As mentioned above, PM2 automatically assigns an application name (based on the filename, without the .js extension) and a PM2 id. PM2 also maintains other information such as the process’s PID, current state, and memory usage.

Applications running under PM2 will automatically restart if the application crashes or is terminated, but we can take the extra step of starting the application at system startup using the startup subcommand. This subcommand generates and configures a startup script to start PM2 and its managed processes when the server boots:

pm2 startup systemd

The last line of the resulting output will include a command run with superuser privileges in order to set PM2 to start on boot:

Output[PM2] Init System found: systemd
sammy
[PM2] To setup the Startup Script, copy/paste the following command:
sudo env PATH=$PATH:/usr/bin /usr/lib/node_modules/pm2/bin/pm2 startup systemd -u sammy --hp /home/sammy

Run the command from the output, substituting your username for sammy:

sudoenvPATH=$PATH:/usr/bin /usr/lib/node_modules/pm2/bin/pm2 startup systemd -usammy--hp /home/sammy

As an additional step, we can save the PM2 process list and corresponding environment:

pm2 save

You have now created a systemd unit that runs pm2 for your user on startup. This pm2 instance in turn runs hello.js.

Start the service with systemctl:

sudo systemctl start pm2-sammy

If you get an error at this point, you may need to reboot, which can be done with sudo reboot.

Check the status of the system unit:

systemctl status pm2-sammy

For a detailed overview of systemd, see systemd Essentials: Working with Services, Units, and Journals.

In addition to what we’ve already covered, PM2 provides a number of subcommands that allow you to manage or find information about applications.

Stop the application with this command (specify PM2 application name or id):

pm2 stop app_name_or_id

Restart the application:

pm2 restart app_name_or_id

List applications currently managed by PM2:

pm2 list

Get information about a specific application using the application name:

pm2 info app_name

The PM2 process monitor can be brought up using the monit subcommand. This will show application status, CPU and memory usage:

pm2 monit

Note that running pm2 without any arguments also displays a help page with example usage.

Now that your Node.js application is running and managed by PM2, let’s set up the reverse proxy.

Step 4 – Set up Nginx as a reverse proxy server

Your application is running and listening on localhost, but you need to set up a way for users to access it. For this, we will setup Nginx web server to act as a reverse proxy.

In the prerequisite tutorial, you set up the Nginx configuration in the /etc/Nginx/sites-available/example.com file. Open this file for editing:

sudonano /etc/nginx/sites-available/example.com

In the server block you should have an existing location/block. Replace the contents of that block with the following configuration. If your application is set to listen on a different port, update the highlighted part to the correct port number:

server {
...
    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;
    }
...
}

This configures the server to respond to requests at its root. Assuming our server is available at example.com, visiting https://example.com/ through a web browser sends a request to hello.js, listening on port 3000 on localhost.

You can add other location blocks to the same server block to provide access to other applications on the same server. For example, if you also have another Node.js application running on port 3001, you can add this location block to allow access via https://example.com/app2:

server {
...
    location /app2 {
        proxy_pass http://localhost:3001;
        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;
    }
...
}

After adding the location block for the application, save the file and exit the editor.

Type the following to make sure you haven’t introduced any syntax errors:

sudo nginx -t

Restart Nginx:

sudo systemctl restart nginx

Assuming your Node.js application is running, and your application and Nginx configuration is correct, you should now be able to access your application through the Nginx reverse proxy. Try it by visiting your server’s URL (either its public IP address or domain name).

in conclusion

Congratulations! Your Node.js application is now running behind an Nginx reverse proxy on your Ubuntu 20.04 server. This reverse proxy setup is flexible enough to allow your users to access other applications or static web content that you want to share.

The knowledge points of the article match the official knowledge files, and you can further learn relevant knowledge. Vue entry skill tree Node.js and npmNode installation and configuration