Articles / DevOps

Run Multiple Node.js Apps on One Server with PM2

Nico Devs·
Run Multiple Node.js Apps on One Server with PM2

I love static sites and serverless functions. With those two, you can build just about anything.

That said, sometimes you need the full power of a server running on your own infrastructure. Maybe you need to run long-lived processes, heavy background jobs, or just host your own apps and services without the constraints of serverless functions.

The challenge starts once you have multiple applications running on the same server, each with its own Node process. How do you keep them running 24/7? How do you make sure they restart if they crash? How do you manage all of them without turning your server into a mess?

That's where PM2 comes in.

PM2 is a process manager for Node.js that makes it easy to run and manage multiple applications. From simple scripts to full-stack Nuxt apps, PM2 helps you orchestrate them and:

  • Monitor and control uptime and memory usage,
  • Automatically restart them if they crash,
  • Check out errors and logs

... and more. Let's check it out.

Installing PM2

Installing PM2 is as simple as installing any other global npm package:

npm install -g pm2

Once installed, run it with pm2. Let's start with the simplest possible example. Imagine you have a file called index.js, you can kickstart it with:

pm2 start index.js

That's it. PM2 will start the process, keep it running, and automatically restart it if it crashes.

You can check all running processes at any time with:

pm2 list

Here's what you'll get:

┌────┬─────────┬─────────┬─────────┬──────────┬──────────┐
│ id │ name    │ status  │ restart │ uptime   │ memory   │
├────┼─────────┼─────────┼─────────┼──────────┼──────────┤
│ 0  │ index   │ online  │ 0       │ 2m       │ 41.8mb   │
└────┴─────────┴─────────┴─────────┴──────────┴──────────┘

And if you want to see the logs:

pm2 logs

... will show you something like:

[TAILING] Tailing last 15 lines for [index] process (change the value with --lines option)

0|index | Server listening on http://localhost:3000
0|index | GET /         200  9ms
0|index | GET /health   200  1ms

Running a Nuxt Application

Running a Nuxt application is just as straightforward. First, build your project:

npm run build

The build produces a .output folder with a self-contained Node server inside it, at .output/server/index.mjs. Instead of running that file directly, hand it to PM2:

pm2 start .output/server/index.mjs --name my-nuxt-app

PM2 will launch the exact same production server you'd get by running that file yourself, but now it will monitor the process, restart it if it crashes, and let you manage it alongside any other applications running on the server.

The Ecosystem File

Once you start running more than a handful of applications, managing everything through individual PM2 commands can become a bit cumbersome. Starting each process one by one, remembering which port it uses, and keeping track of its configuration doesn't scale very well.

A much better approach is to define everything in a single ecosystem file. It's a plain JavaScript config where you describe every application you want PM2 to manage: its name, the script to run, its working directory (cwd), environment variables, ports, log files, restart policy, and any other setting you need.

For example:

ecosystem.config.js
module.exports = {
  apps: [
    {
      name: "web-app",
      cwd: "/var/www/web-app",
      script: "index.js",
      env: {
        PORT: 3000,
      },
    },
    {
      name: "my-nuxt-app",
      cwd: "/var/www/my-nuxt-app",
      script: ".output/server/index.mjs",
      env: {
        PORT: 3001,
      },
    },
    {
      name: "email-worker",
      cwd: "/var/www/email-worker",
      script: "worker.js",
      error_file: "./logs/error.log",
      out_file: "./logs/output.log",
    },
  ],
};

Once you have your ecosystem file, starting every application is just a single command:

pm2 start ecosystem.config.js

You can also restart, stop, or delete the entire group just as easily:

pm2 restart ecosystem.config.js
pm2 stop ecosystem.config.js
pm2 delete ecosystem.config.js

With that in place, your entire stack can be managed from a single configuration file instead of dozens of individual commands. It also makes your server configuration reproducible, easy to version control, and much easier to maintain as your infrastructure grows.

Reverse Proxies

If you have multiple Nuxt apps, each one runs on a different port, like 3000 or 3001.

So how does one server know that marketing.com should go to the app on port 3000, while dashboard.com goes to the one on port 3001?

That's the job of a reverse proxy. A web server like Nginx sits in front of your apps, reads the domain of each incoming request, and forwards it to the right local port. A minimal configuration for a single site looks like this:

/etc/nginx/sites-available/marketing
server {
  listen 80;
  server_name marketing.com;

  location / {
    proxy_pass http://localhost:3000;
    proxy_set_header Host $host;
  }
}

You write one of these blocks per domain, each pointing at the port of its app, and Nginx takes care of the routing from there.

Configuring Nginx from scratch (and adding HTTPS with something like Let's Encrypt) is a topic of its own, but this is the piece that connects your PM2 processes to the outside world.

Surviving a Server Reboot

At this point, your applications are running under PM2, but there's still one important problem left to solve. You might be wondering:

What happens if the server reboots?

By default, PM2 won't automatically restore your applications after a system restart. Fortunately, enabling that behavior only takes a couple of commands.

First, save the list of applications that are currently running:

pm2 save

This creates a snapshot of your current processes, including everything defined in your ecosystem file.

Next, tell your operating system to start PM2 automatically during boot:

pm2 startup

PM2 will detect your operating system and print a command to the terminal. Simply copy and run that command once. It registers PM2 as a system service so it starts automatically whenever the server boots.

From that point on, every time your VPS restarts, PM2 will start automatically and restore all the processes you previously saved.

It's a small step, and one of the easiest to forget. It's also one of the most important, since it's what lets your applications come back on their own after a reboot, with no manual intervention.

In Closing

PM2 is one of those tools you can learn in an afternoon and keep using for years.

If you're hosting your own Node.js applications, it solves a surprising number of problems: it keeps your processes alive, manages multiple services at once, organizes your infrastructure into a single ecosystem file, and brings everything back online after a server reboot.

The best part is that, once it's set up, it doesn't demand much attention. It quietly does its job while you focus on building your applications instead of worrying about whether they'll still be running tomorrow.

If you give it a try on your own server, I'd like to hear how it goes!

More in DevOps