Skip to content

How to use pm2 to serve a Node.js app

An introduction on how to manage Node processes on Linux and auto-restart them through GitHub webhooks

pm2 is a super useful process management tool for Linux.

I’ve used it for several projects of mine, and here I want to tell you how to use it too!

In particular I’m going to use it to run a Node.js app on a DigitalOcean VPS, and I’ll set it up so that whenever we push an update to the app’s GitHub repository, on the server pm2 will be pinged, and the app is going to be updated from GitHub and restarted.

Sounds cool? Let’s go!

First, sign up to DigitalOcean and follow my tutorial to create an VPS on DigitalOcean.

Important: use the NodeJS image on DigitalOcean, which is already set up with pm2 and node, and has a nodejs user in addition to root.

Once you’re up and running we can start.

Make sure you ssh as the nodejs user. When you’re logged in as root, you can just run su nodejs to use that user.

The sample Node.js app you have running in the VPS is in the folder /var/www/html/, and it’s composed by the hello.js file.

The deployment/running of the application is already managed through the pm2 program, a daemon process manager.

You can use pm2 list to see all the applications running now:

If you do any change to the application now, the changes won’t be applied until you restart the application running:

pm2 restart hello

You can stop the application running

pm2 stop hello

and this will give you an error in the browser if you try to reload the page:

You can run:

pm2 start hello

to bring the app up again.

The nice thing about pm2 is that it will take care of running the app(s) again when the system is restarted.

Now that you’ve seen how the built-in hello world works, let’s deploy another application.

Let’s stop the current sample app:

pm2 stop hello

and create a test folder in /var/www/html:

mkdir test

go into that

cd test

and run

npm init -y

then install Express:

npm install express

Now run nano app.js and add this code:

const express = require("express")
const app = express()

app.get("/", (*req*, *res*) => res.send("Hello World!"))
app.listen(3000, () => console.log("Server ready"))

Run

pm2 start app.js --name app

and you should see the app running!

We used port 3000, which is what the hello app used, so we did not have to configure anything else.

Otherwise we’d need to modify the /etc/nginx/sites-available/default and add a new URL to map to our app.

Now that we deployed a sample app, let’s see how to deploy an app from GitHub.

First thing, let’s stop the app we just built in the previous lesson:

pm2 stop app

and let’s make a sample GitHub app. It’s not going to be complex, it’s very similar to the app we built in the previous lesson, but it will say Hello World, deployed from GitHub! instead of just Hello World!

On your local create a folder, and inside it run

npm init -y

then install Express:

npm install express

Now run nano app.js and add this code:

const express = require("express")
const app = express()

app.get("/", (*req*, *res*) => res.send("Hello World, deployed from GitHub!"))
app.listen(3000, () => console.log("Server ready"))

Now it’s time to push the app to GitHub. I use GitHub Desktop, the official GitHub app, so I drag and drop the folder into the app, and it lets me create a new Git repo, and a GitHub repository.

I initialized the repo with the standard Node.js .gitignore file, which ignored the node_modules folder. We’ll see how to initialize the npm modules during deployment.

Now we have to create a SSH key on the DigitalOcean machine, and add it to GitHub. Otherwise we will not have access to the private repository we just created.

Double-check the email address you used to connect to GitHub, because it’s important. You can find it in the GitHub settings, in the Email panel. Then run:

ssh-keygen -t rsa -b 4096 -C "[email protected]"

Now open the “SSH and GPG keys” panel on GitHub:

and click “New SSH key”. Enter “DigitalOcean” as the title, so you know what this key is used for.

Now type

less /home/nodejs/.ssh/id_rsa.pub

on your DigitalOcean droplet to get the public key.

Copy that to your settings, and save. You should see it in the SSH keys list.

Now we should be able to clone the private repository on the server.

Go in the /var/www/html folder, and run

git clone [email protected]:<yourname>/<reponame>.git

In my case it was:

git clone [email protected]:flaviocopes/testdo.git

and the app was successfully downloaded:

Now type cd testdo and run npm install.

The app is now ready to run, so type

pm2 stop test

to stop the previous app, and

pm2 start app.js --name testdo

to start the new one:

Install the pm2 module called pm2-githook https://github.com/vmarchaud/pm2-githook using this command:

pm2 install pm2-githook

To configure the app, we need to use this command, which requires correct escaping of double quotes:

pm2 set pm2-githook:apps "{\"testdo\":{\"secret\":\"test123\",\"prehook\":\"npm install --production\",\"posthook\":\"echo done\"}}"

In this example I used the secret value test123, but change it to something less easy to guess.

By default this module listens on port 8888 for Webhooks coming from GitHub.

This port is now closed, open it using

sudo ufw allow 8888

Now open the repository settings on GitHub, and in the Webhooks section click Add Webhook.

Paste the URL of your domain, with port 8888.

In this example I used the secret value test123.

Now if you try doing a change to your code, it should automatically be deployed!


→ Here's my latest YouTube video

→ Get my Linux Command Line Handbook

→ I wrote 17 books to help you become a better developer, download them all at $0 cost by joining my newsletter

JOIN MY CODING BOOTCAMP, an amazing cohort course that will be a huge step up in your coding career - covering React, Next.js - next edition February 2025

Bootcamp 2025

Join the waiting list