# How to use pm2 to serve a Node.js app

> Learn how to use pm2 to run a Node.js app on a Linux VPS, manage it with commands like pm2 list and pm2 restart, and auto-restart it via GitHub webhooks.

Author: [Flavio Copes](https://flaviocopes.com/about/) | Published: 2021-03-03 | Topics: [CLI](https://flaviocopes.com/tags/cli/) | Canonical: https://flaviocopes.com/pm2/

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!

If you prefer to run your Node app with systemd instead of pm2, I built a free [systemd service generator](https://flaviocopes.com/tools/systemd-generator/) that writes the unit file and the install commands for you.

In particular I'm going to use it to run a [Node.js](https://flaviocopes.com/nodejs/) app on a DigitalOcean VPS, and I'll set it up so that whenever we push an update to the app's [GitHub](https://flaviocopes.com/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](https://flaviocopes.com/digitalocean-create-vps/).

> 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:

![Terminal showing pm2 list command output with one process named hello running in fork mode with online status](https://flaviocopes.com/images/pm2/Screen_Shot_2020-08-09_at_14.41.16.png)

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

```
pm2 restart hello
```

![Terminal output showing pm2 restart hello command execution and updated process list with restart count of 1](https://flaviocopes.com/images/pm2/Screen_Shot_2020-08-09_at_14.42.56.png)

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:

![Browser showing 502 Bad Gateway error from nginx when the pm2 process is stopped](https://flaviocopes.com/images/pm2/Screen_Shot_2020-08-09_at_14.43.19.png)

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:

```js
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!

![Browser displaying Hello World message after starting the Express app with pm2](https://flaviocopes.com/images/pm2/Screen_Shot_2020-08-09_at_15.10.17.png)

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:

```js
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](https://flaviocopes.com/git/) repo, and a GitHub repository.

![GitHub Desktop showing Publish Repository dialog with testdo repository name and Keep this code private option checked](https://flaviocopes.com/images/pm2/Screen_Shot_2020-08-09_at_15.56.14.png)

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](https://flaviocopes.com/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 "your_email@example.com"
```

Now open the "SSH and GPG keys" panel on GitHub:

![GitHub SSH and GPG keys settings page showing empty SSH keys section with New SSH key button](https://flaviocopes.com/images/pm2/Screen_Shot_2020-08-09_at_16.04.29.png)

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 git@github.com:<yourname>/<reponame>.git
```

In my case it was:

```
git clone git@github.com:flaviocopes/testdo.git
```

and the app was successfully downloaded:

![Terminal showing successful git clone of testdo repository from GitHub with download progress](https://flaviocopes.com/images/pm2/Screen_Shot_2020-08-09_at_16.10.19.png)

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:

![Browser showing Hello World deployed from GitHub message after starting the GitHub-cloned app with pm2](https://flaviocopes.com/images/pm2/Screen_Shot_2020-08-09_at_16.12.00.png)

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**.

![GitHub repository Webhooks settings page showing Add webhook button and explanation text](https://flaviocopes.com/images/pm2/Screen_Shot_2020-08-09_at_16.33.29.png)

![GitHub Add webhook form with payload URL field, content type dropdown, and event trigger options](https://flaviocopes.com/images/pm2/Screen_Shot_2020-08-09_at_16.33.42.png)

Paste the URL of your domain, with port 8888.

In this example I used the `secret` value `test123`.

![GitHub webhook configuration form showing payload URL with test123 secret and Just the push event selected](https://flaviocopes.com/images/pm2/Screen_Shot_2020-08-09_at_17.34.15.png)

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

![Browser displaying updated Hello World UPDATED and deployed from GitHub message showing automatic deployment works](https://flaviocopes.com/images/pm2/Screen_Shot_2020-08-09_at_17.32.41.png)
