Side note: Get alerted when your Node.js app goes down
Head over to Better Stack and start monitoring your endpoints in 2 minutes.
PM2 is a renowned open-source process manager tailored for Node.js applications. It acts as a guardian, streamlining deployment, overseeing logs, monitoring resources, and ensuring minimal downtime for every application it manages.
This article will introduce you to the key features of PM2 and help you leverage it for deploying, overseeing, and scaling your Node.js applications effectively in a production environment.
Before proceeding with this tutorial, ensure that you have a recent version of Node.js and npm installed on your machine.
Head over to Better Stack and start monitoring your endpoints in 2 minutes.
In this guide, we'll explore various functionalities of PM2 using a sample Node.js application. If you have your project, you're welcome to use it as you follow along. If not, let's get started by cloning the demo app repository:
git clone https://github.com/betterstack-community/dadjokes
Once cloned, navigate into the project directory and set up its required dependencies:
cd dadjokes && npm install
This application fetches a humorous dad joke from the ICanHazdadjokes API and displays it on a webpage. To see it in action, initiate the server using:
node server.js
dadjokes server started on port: 3000
With the server up and running, open your browser and head to http://localhost:3000. Here's what you can expect to see:
Having set up the demo app, we'll transition to installing PM2's npm package in the next section.
To harness the power of PM2 for managing your Node.js applications, you'll first need to install its npm package as follows:
npm install -g pm2
Go ahead and confirm the version you have installed through the following command:
pm2 --version
Here's the expected output:
5.2.0
Interestingly, your PM2 installation actually provides four distinct executables:
pm2
: The primary PM2 binary.pm2-dev
: A development tool akin to
nodemon that auto-restarts the
Node.js process during development.pm2-runtime
: A seamless alternative to the node command, tailored for
containerized environments.pm2-docker
: Essentially an alias for pm2-runtime
.In the sections ahead, we'll delve into the specific applications of each of
these executables. But for now, let's focus on establishing a foundational
development workflow using the pm2-dev
binary in the next step.
While PM2 is primarily renowned for its usefulness in production, it can also
serve you during the development phase through the pm2-dev
binary. When you
kickstart your application using pm2-dev
, it monitors the current directory.
Your application gets a swift restart if there are any changes in the directory
or its subdirectories.
To set things in motion, first ensure that any previously running instance of
your server is terminated with Ctrl-C
. Then launch your server in development
mode:
pm2-dev server.js
You should observe the following output:
===============================================================================
--- PM2 development mode ------------------------------------------------------
Apps started : server
Processes started : 1
Watch and Restart : Enabled
Ignored folder : node_modules
===============================================================================
server-0 | dadjokes server started on port: 3000
At this point, changing any file in the dadjokes
directory will cause the
server to restart. Try it out by modifying your server.js
file as shown below.
Change the title
property from 'Random Jokes' to 'Random Dad Jokes' and save
the file:
. . .
app.get('/', async (req, res, next) => {
try {
const response = await getRandomJoke();
res.render('home', {
title: 'Random Dad Jokes',
dadjokes: response,
});
} catch (err) {
next(err);
}
});
. . .
Now, shift your attention to the terminal where your development server is active. You'll notice an output indicating PM2's awareness of the change and its subsequent action to restart the server:
. . .
[rundev] App server restarted
server-0 | dadjokes server started on port: 3000
For a deeper dive into pm2-dev
and its capabilities, use the --help
flag.
It'll unveil the available options to tailor pm2-dev
to your needs. Up next,
we'll transition to the core features of the primary pm2
binary.
When it's time to shift from development to production, the pm2 start
command
is one to use. First, ensure you've terminated any running development server
with Ctrl-C
. Then, to launch your application in a production setting, use:
pm2 start --name 'dadjokes' server.js
You should see the following output:
[PM2] Starting /home/ayo/dev/demo/dadjokes/server.js in fork_mode (1 instance)
[PM2] Done.
βββββββ¬ββββββββββββββ¬ββββββββββββββ¬ββββββββββ¬ββββββββββ¬βββββββββββ¬βββββββββ¬βββββββ¬ββββββββββββ¬βββββββββββ¬βββββββββββ¬βββββββββββ¬βββββββββββ
β id β name β namespace β version β mode β pid β uptime β βΊ β status β cpu β mem β user β watching β
βββββββΌββββββββββββββΌββββββββββββββΌββββββββββΌββββββββββΌβββββββββββΌβββββββββΌβββββββΌββββββββββββΌβββββββββββΌβββββββββββΌβββββββββββΌβββββββββββ€
β 0 β dadjokes β default β 1.0.0 β fork β 1658 β 0s β 0 β online β 0% β 18.3mb β ayo β disabled β
βββββββ΄ββββββββββββββ΄ββββββββββββββ΄ββββββββββ΄ββββββββββ΄βββββββββββ΄βββββββββ΄βββββββ΄ββββββββββββ΄βββββββββββ΄βββββββββββ΄βββββββββββ΄βββββββββββ
This table confirms the successful background launch of the dadjokes
application. If your terminal window is compact, you might see fewer columns, so
consider expanding it for a comprehensive view.
Here's a breakdown of the table columns:
id
: A unique identifier assigned by pm2
to the application.name
: The application's name. If the --name
flag isn't specified, it
defaults to the entry file name (in this case, server.js
).namespace
: Allows for batch operations (like start/stop/restart) on a group
of apps.version
: The version number sourced from the app's package.json
.mode
: Can be either fork
or cluster
. It directs pm2
to utilize the
child_process.fork
or the cluster API.pid
: The process's unique identifier.uptime
: Duration since the application's launch. βΊ:
Counts the number of
application restarts. status
: Current application status. cpu
: Percentage
of CPU usage. mem
: Memory consumption. user
: The username of the
individual who initiated the application. watching
: Indicates if pm2 is set
to auto-restart the app upon detecting file changes in the directory or its
subdirectories. This is controlled by the --watch
option.At this point, you can visit http://localhost:3000 in your browser and everything should work just fine as before.
If you need your application to connect to various services (such as databases,
caches, etc) on start up, you can use the --wait-ready
flag which instructs
PM2 to await a ready
signal from your application before marking it as
"online". Here's how to implement this:
. . .
const server = app.listen(process.env.PORT || 3000, () => {
console.log(`dadjokes server started on port: ${server.address().port}`);
// simulate a ready application after 1 second
setTimeout(function () {
process.send('ready');
}, 1000);
});
After integrating the highlighted lines into your application, you'll need to
delete the current instance and restart it, this time using the --wait-ready
flag:
pm2 delete dadjokes
pm2 start --name 'dadjokes' server.js --wait-ready
[PM2] Starting /home/ayo/dev/demo/dadjokes/server.js in fork_mode (1 instance)
[PM2] Done.
βββββββ¬ββββββββββββββ¬ββββββββββββββ¬ββββββββββ¬ββββββββββ¬βββββββββββ¬βββββββββ¬βββββββ¬ββββββββββββ¬βββββββββββ¬βββββββββββ¬βββββββββββ¬βββββββββββ
β id β name β namespace β version β mode β pid β uptime β βΊ β status β cpu β mem β user β watching β
βββββββΌββββββββββββββΌββββββββββββββΌββββββββββΌββββββββββΌβββββββββββΌβββββββββΌβββββββΌββββββββββββΌβββββββββββΌβββββββββββΌβββββββββββΌβββββββββββ€
β 0 β dadjokes β default β 1.0.0 β fork β 18529 β 1s β 0 β online β 0% β 57.4mb β ayo β disabled β
βββββββ΄ββββββββββββββ΄ββββββββββββββ΄ββββββββββ΄ββββββββββ΄βββββββββββ΄βββββββββ΄βββββββ΄ββββββββββββ΄βββββββββββ΄βββββββββββ΄βββββββββββ΄βββββββββββ
By default, if the ready
signal isn't received, PM2 will wait for three
seconds before designating your application as online. If you wish to adjust
this duration, you can use the --listen-timeout
flag followed by the desired
timeout in milliseconds:
pm2 start --name 'dadjokes' server.js --wait-ready --listen-timeout 10000
Up next, we'll delve into how you can monitor your application's resource consumption and other pertinent metrics using PM2.
With your application in motion, PM2 offers a suite of subcommandsβlist
,
show
, and monit
βto help you monitor its performance. To get an overview of
all active applications on your server, use:
pm2 list
You should observe the following output:
βββββββ¬ββββββββββββββ¬ββββββββββββββ¬ββββββββββ¬ββββββββββ¬βββββββββββ¬βββββββββ¬βββββββ¬ββββββββββββ¬βββββββββββ¬βββββββββββ¬βββββββββββ¬βββββββββββ
β id β name β namespace β version β mode β pid β uptime β βΊ β status β cpu β mem β user β watching β
βββββββΌββββββββββββββΌββββββββββββββΌββββββββββΌββββββββββΌβββββββββββΌβββββββββΌβββββββΌββββββββββββΌβββββββββββΌβββββββββββΌβββββββββββΌβββββββββββ€
β 0 β dadjokes β default β 1.0.0 β fork β 18529 β 3m β 0 β online β 0% β 53.3mb β ayo β disabled β
βββββββ΄ββββββββββββββ΄ββββββββββββββ΄ββββββββββ΄ββββββββββ΄βββββββββββ΄βββββββββ΄βββββββ΄ββββββββββββ΄βββββββββββ΄βββββββββββ΄βββββββββββ΄βββββββββββ
This command provides a snapshot of each application's status, uptime, memory usage, and more. If you have multiple applications running, you can sort them based on specific metrics:
pm2 list --sort [name|id|pid|memory|cpu|status|uptime][:asc|desc]
As in:
pm2 list --sort memory:desc
For a more detailed look at a specific application, use the pm2 show
command
followed by the application's name or id:
pm2 show dadjokes
[s/t]
For a live dashboard displaying metrics, metadata, and application logs, use:
pm2 monit
Head over to Better Uptime start monitoring your endpoints in 2 minutes
PM2's auto-restart capability is a boon for ensuring
application uptime. You can also restart your
application manually through the pm2 restart
command as follows:
pm2 restart dadjokes
[PM2] Applying action restartProcessId on app [dadjokes](ids: [ 0 ])
[PM2] [dadjokes](0) β
βββββββ¬ββββββββββββββ¬ββββββββββββββ¬ββββββββββ¬ββββββββββ¬βββββββββββ¬βββββββββ¬βββββββ¬ββββββββββββ¬βββββββββββ¬βββββββββββ¬βββββββββββ¬βββββββββββ
β id β name β namespace β version β mode β pid β uptime β βΊ β status β cpu β mem β user β watching β
βββββββΌββββββββββββββΌββββββββββββββΌββββββββββΌββββββββββΌβββββββββββΌβββββββββΌβββββββΌββββββββββββΌβββββββββββΌβββββββββββΌβββββββββββΌβββββββββββ€
β 0 β dadjokes β default β 1.0.0 β fork β 4873 β 1s β 1 β online β 0% β 52.6mb β ayo β disabled β
βββββββ΄ββββββββββββββ΄ββββββββββββββ΄ββββββββββ΄ββββββββββ΄βββββββββββ΄βββββββββ΄βββββββ΄ββββββββββββ΄βββββββββββ΄βββββββββββ΄βββββββββββ΄βββββββββββ
Notice that the restart column (βΊ) has been incremented to 1
, indicating that
the application has been restarted once.
PM2's default behavior is to restart your application even if it exits
intentionally. For instance, triggering the /graceful-shutdown
route in the
dadjokes application will gracefully shut it down by calling process.exit(0)
:
curl http://localhost:3000/graceful-shutdown
Graceful shutdown!
On the other hand, accessing the /crashme
route simulates an application
crash:
curl http://localhost:3000/crashme
Crashing server!
After these actions, executing the pm2 list
command will show the restart
count has risen to 3, yet the application status remains online:
pm2 list
βββββββ¬ββββββββββββββ¬ββββββββββββββ¬ββββββββββ¬ββββββββββ¬βββββββββββ¬βββββββββ¬βββββββ¬ββββββββββββ¬βββββββββββ¬βββββββββββ¬βββββββββββ¬βββββββββββ
β id β name β namespace β version β mode β pid β uptime β βΊ β status β cpu β mem β user β watching β
βββββββΌββββββββββββββΌββββββββββββββΌββββββββββΌββββββββββΌβββββββββββΌβββββββββΌβββββββΌββββββββββββΌβββββββββββΌβββββββββββΌβββββββββββΌβββββββββββ€
β 0 β dadjokes β default β 1.0.0 β fork β 10008 β 2s β 3 β online β 0% β 53.5mb β ayo β disabled β
βββββββ΄ββββββββββββββ΄ββββββββββββββ΄ββββββββββ΄ββββββββββ΄βββββββββββ΄βββββββββ΄βββββββ΄ββββββββββββ΄βββββββββββ΄βββββββββββ΄βββββββββββ΄βββββββββββ
Currently, PM2 doesn't offer the flexibility to adjust restart behavior based on
an application's exit code. Although the --stop-exit-codes
flag is
documented, it seems
non-functional in the recent versions. This limitation is being tracked in an
open GitHub issue.
pm2 start --stop-exit-codes 0 --name 'dadjokes' server.js
error: unknown option `--stop-exit-codes'
In the next step, you will configure other auto-restart strategies through the PM2 configuration file.
PM2 employs an ecosystem.config.js
file to consolidate
configuration settings
for one or more applications. To generate this file in your project directory,
use:
pm2 init simple
Upon execution, you should see:
File /home/ayo/dev/demo/dadjokes/ecosystem.config.js generated
Edit the ecosystem.config.js
file and update the name
and script
fields to
match your application:
module.exports = {
apps: [{
name: 'dadjokes',
script: './server.js',
}]
}
With this configuration, you can manage all declared applications:
pm2 start ecosystem.config.js
pm2 restart ecosystem.config.js
pm2 stop ecosystem.config.js
Let's now delve into advanced restart strategies:
PM2 can restart an application upon reaching a memory limit, preventing
potential "heap out of memory errors". Configure this using the
max_memory_restart
option:
module.exports = {
apps: [{
name: 'dadjokes',
script: './server.js',
max_memory_restart: '1G',
}]
}
This restarts the dadjokes application if memory usage surpasses 1 Gigabyte. You
can also configure the max_memory_restart
option in Kilobyte (K), and Megabyte
(M).
PM2 can restart applications based on a cron schedule. For daily restarts:
module.exports = {
apps: [{
name: 'dadjokes',
script: './server.js',
cron_restart: '0 */24 * * *',
}]
}
To learn and test the cron syntax, consider using the crontab guru editor.
You can introduce a delay before PM2 restarts an application using the
restart_delay
option:
module.exports = {
apps: [{
name: 'dadjokes',
script: './server.js',
restart_delay: 5000 // wait for five seconds before restarting
}]
}
Instead of setting a fixed delay before restarting the application, you can use
the exp_backoff_restart_delay
option to to raise the time between restarts up
to 15 seconds incrementally. The initial delay time set through this option will
be multiplied by 1.5 after each restart attempt.
module.exports = {
apps: [{
name: 'dadjokes',
script: './server.js',
exp_backoff_restart_delay: 100 // 100ms
}]
}
With the above in place, the first restart attempt will be delayed by 100ms, second restart 150ms, then 225ms, 337.5ms, 506.25ms, and so on. This delay is reset to 0ms if the application remains online for over 30 seconds.
PM2 provides a max_restarts
option for configuring the maximum number of
unstable restarts before the application is considered to have encountered an
unrecoverable error. This lets you prevent your application from constantly
dying and restarting, which may waste resources.
You can also specify an unstable restart through the min_uptime
option. This
allows you to specify the amount of time before your application is considered
"online":
module.exports = {
apps: [{
name: 'dadjokes',
script: './server.js',
max_restarts: 16,
min_uptime: 5000, // 5 seconds
}]
}
Once an application has reached the maximum number of restarts, it will display
an errored
status. Your intervention will be required before the app can be up
and running once again.
βββββββ¬βββββββββββββββββ¬ββββββββββββββ¬ββββββββββ¬ββββββββββ¬βββββββββββ¬βββββββββ¬βββββββ¬ββββββββββββ¬βββββββββββ¬βββββββββββ¬βββββββββββ¬βββββββββββ
β id β name β namespace β version β mode β pid β uptime β βΊ β status β cpu β mem β user β watching β
βββββββΌβββββββββββββββββΌββββββββββββββΌββββββββββΌββββββββββΌβββββββββββΌβββββββββΌβββββββΌββββββββββββΌβββββββββββΌβββββββββββΌβββββββββββΌβββββββββββ€
β 0 β dadjokes β default β 1.0.0 β fork β 0 β 0 β 15 β errored β 0% β 0b β ayo β disabled β
βββββββ΄βββββββββββββββββ΄ββββββββββββββ΄ββββββββββ΄ββββββββββ΄βββββββββββ΄βββββββββ΄βββββββ΄ββββββββββββ΄βββββββββββ΄βββββββββββ΄βββββββββββ΄βββββββββββ
To turn off automatic restarts entirely, set the autorestart
option to
false
:
module.exports = {
apps: [{
name: 'dadjokes',
script: './server.js',
autorestart: false,
}]
}
Now, go ahead and integrate these strategies into your ecosystem.config.js
:
module.exports = {
apps: [
{
name: 'dadjokes',
script: './server.js',
exp_backoff_restart_delay: 100,
max_memory_restart: '1G',
max_restarts: 10,
min_uptime: 2000,
},
],
};
Remove the existing dadjokes
instance:
pm2 delete dadjokes
Then, relaunch it using the configuration file:
pm2 start ecosystem.config.js
In the next section, you will discover how to start your Node.js application automatically even after a system reboot.
In the last section, we explored methods to keep your application resilient
against unexpected crashes. In this segment, we'll delve into how to set up a
Node.js application to autostart after system reboots or crashes using PM2 and
Systemd on Linux. Although PM2 is
compatible with various init systems, including upstart
, launchd
, openrc
,
rcd
, and systemv
, this guide focuses on Systemd.
Begin by generating an init script for your system. This script will initiate the pm2 daemon during system startup, which in turn will launch any saved applications:
pm2 startup systemd
The output should resemble:
[PM2] Init System found: systemd
[PM2] To setup the Startup Script, copy/paste the following command:
sudo env PATH=$PATH:/home/<username>/.local/share/nvm/v20.5.0/bin /home/<username>/.local/share/nvm/v20.5.0/lib/node_modules/pm2/bin/pm2 startup systemd -u <username> --hp /home/<username>
Go ahead and run the generated command shown above to configure the Systemd startup script:
sudo env PATH=$PATH:/home/<username>/.local/share/nvm/v16.14.0/bin /home/<username>/.local/share/nvm/v16.14.0/lib/node_modules/pm2/bin/pm2 startup systemd -u <username> --hp /home/<username>
A successful command will yield:
[PM2] Init System found: systemd
. . .
[PM2] [v] Command successfully executed.
+---------------------------------------+
[PM2] Freeze a process list on reboot via:
$ pm2 save
[PM2] Remove init script via:
$ pm2 unstartup systemd
If you encounter an error, it might indicate that the pm2
service file was
created but couldn't be enabled. In such cases, manually enable the service:
sudo systemctl enable pm2-<username>
To ensure that PM2 automatically starts any saved processes upon system boot, save your PM2 process list:
pm2 save
pm2 save
[PM2] Saving current process list...
[PM2] Successfully saved in /home/<username>/.pm2/dump.pm2
Now, PM2 is set to launch saved processes on system boot automatically, and you
can update this list anytime with pm2 save
. Test this by rebooting your system
and running pm2 list
afterward. If any issues arise, use the following command
to launch all applications:
pm2 resurrect
To deactivate the startup script, employ the pm2 unstartup
command. It's a
good practice to do this whenever you upgrade Node.js, ensuring that the
subsequent pm2
startup uses the updated Node.js binary.
If you need to disable the startup script, use the pm2 unstartup
command. You
should always do this after upgrading your Node.js version so that when you run
pm2 startup
again, it'll generate a startup configuration that uses the latest
Node.js binary.
At this point, you've learned all the ways PM2 ensures your application's resilience against external disruptions like system reboots. For continuous monitoring of your application's online status, consider implementing uptime monitoring checks for timely notifications on server availability and other related issues.
PM2 offers the stop
and delete
commands to halt an application and expunge
it from the process list, respectively. These commands can target specific
applications by their names, ids, namespaces, configuration files, or you can
use the all
keyword to target every application.
To halt all applications defined in the ecosystem.config.js
file, use:
pm2 stop ecosystem.config.js
[PM2] [dadjokes](0) β
βββββββ¬ββββββββββββββ¬ββββββββββββββ¬ββββββββββ¬ββββββββββ¬βββββββββββ¬βββββββββ¬βββββββ¬ββββββββββββ¬βββββββββββ¬βββββββββββ¬βββββββββββ¬βββββββββββ
β id β name β namespace β version β mode β pid β uptime β βΊ β status β cpu β mem β user β watching β
βββββββΌββββββββββββββΌββββββββββββββΌββββββββββΌββββββββββΌβββββββββββΌβββββββββΌβββββββΌββββββββββββΌβββββββββββΌβββββββββββΌβββββββββββΌβββββββββββ€
β 0 β dadjokes β default β 1.0.0 β fork β 0 β 0 β 3 β stopped β 0% β 0b β ayo β disabled β
βββββββ΄ββββββββββββββ΄ββββββββββββββ΄ββββββββββ΄ββββββββββ΄βββββββββββ΄βββββββββ΄βββββββ΄ββββββββββββ΄βββββββββββ΄βββββββββββ΄ββββββββββοΏ½οΏ½οΏ½βββββββββββ
When you pass the all
keyword, it causes the delete
subcommand to act on all
the applications in the list:
pm2 delete all
[PM2] Applying action deleteProcessId on app [all](ids: [ 0 ])
[PM2] [dadjokes](0) β
βββββββ¬ββββββββββββ¬ββββββββββββββ¬ββββββββββ¬ββββββββββ¬βββββββββββ¬βββββββββ¬βββββββ¬ββββββββββββ¬βββββββββββ¬βββββββββββ¬βββββββββββ¬βββββββββββ
β id β name β namespace β version β mode β pid β uptime β βΊ β status β cpu β mem β user β watching β
βββββββ΄ββββββββββββ΄ββββββββββββββ΄ββββββββββ΄ββββββββββ΄βββββββββββ΄βββββββββ΄βββββββ΄ββββββββββββ΄βββββββββββ΄βββββββββββ΄βββββββββββ΄βββββββββββ
After deleting your application, you may need to run pm2 save
to update
process list dump file.
To guarantee a smooth shutdown when employing the restart
, stop
, reload
,
or delete
commands, it's crucial to catch the SIGINT
or SIGTERM
signals.
This ensures that any essential cleanup tasks are executed before the
application terminates, such as processing all current requests and releasing
resources like files and database connections.
Below is a sample that stops the server from accepting new connections when the
SIGINT
and SIGTERM
signals are detected, and then terminates the process:
. . .
function cleanupAndExit() {
server.close(() => {
console.log('dadjokes server closed');
process.exit(0);
});
}
process.on('SIGTERM', cleanupAndExit);
process.on('SIGINT', cleanupAndExit);
PM2 automatically archives logs generated by your application in the
$HOME/.pm2/logs
directory. Logs directed to the standard output are stored in
the <app_name>-out.log
file, while logs directed to the standard error are
stored in the <app_name>-error.log
file.
ls ~/.pm2/logs
dadjokes-error.log dadjokes-out.log
Within the ecosystem.config.js
file, you can define the error_file
and
out_file
options to specify custom locations for the application's error and
output log files:
module.exports = {
apps: [
{
name: 'dadjokes',
script: './server.js',
exp_backoff_restart_delay: 100,
max_memory_restart: '1G',
max_restarts: 10,
min_uptime: 2000,
out_file: '<custom_path>', // use /dev/null to disable
error_file: '<custom_path>', // use /dev/null to disable
},
],
};
To ensure that your application log files are rotated before they get too large, install the pm2-logrotate module as shown below:
pm2 install pm2-logrotate
Afterward, when you run pm2 list
, you should see a new "Module" section like
so:
βββββββ¬βββββββββββββββββββ¬ββββββββββββββ¬ββββββββββ¬ββββββββββ¬βββββββββββ¬βββββββββ¬βββββββ¬ββββββββββββ¬βββββββββββ¬βββββββββββ¬βββββββββββ¬βββββββββββ
β id β name β namespace β version β mode β pid β uptime β βΊ β status β cpu β mem β user β watching β
βββββββΌβββββββββββββββββββΌββββββββββββββΌββββββββββΌββββββββββΌβββββββββββΌβββββββββΌβββββββΌββββββββββββΌβββββββββββΌβββββββββββΌβββββββββββΌβββββββββββ€
β 0 β dadjokes β default β 1.0.0 β fork β 9408 β 3m β 0 β online β 0% β 50.2mb β ayo β disabled β
βββββββ΄βββββββββββββββββββ΄ββββββββββββββ΄ββββββββββ΄ββββββββββ΄βββββββββββ΄βββββββββ΄βββββββ΄ββββββββββββ΄βββββββββββ΄βββββββββββ΄βββββββββββ΄βββββββββββ
Module
ββββββ¬βββββββββββββββββββββββββββββββ¬ββββββββββββββββ¬βββββββββββ¬βββββββββββ¬βββββββ¬βββββββββββ¬βββββββββββ¬βββββββββββ
β id β module β version β pid β status β βΊ β cpu β mem β user β
ββββββΌβββββββββββββββββββββββββββββββΌββββββββββββββββΌβββββββββββΌβββββββββββΌβββββββΌβββββββββββΌβββββββββββΌβββββββββββ€
β 1 β pm2-logrotate β 2.7.0 β 12068 β online β 0 β 0% β 21.4mb β ayo β
ββββββ΄βββββββββββββββββββββββββββββββ΄ββββββββββββββββ΄βββββββββββ΄βββββββββββ΄βββββββ΄βββββββββββ΄βββββββββββ΄βββββββββββ
By default, the pm2-logrotate
module rotates log files once they exceed 10
megabytes. It retains up to 30 rotated files and deletes older ones. Backup
files are not compressed by default. You can adjust these settings and more by
referring to the module's documentation, or you use
logrotate to handle
log file rotation instead.
The pm2 logs
command may come in handy in development for displaying incoming
application log entries in real time:
pm2 logs dadjokes
PM2 | 2022-03-14T09:18:30: PM2 log: App [dadjokes:0] starting in -fork mode-
PM2 | 2022-03-14T09:18:30: PM2 log: App [dadjokes:0] online
PM2 | 2022-03-14T09:23:18: PM2 log: Stopping app:dadjokes id:0
PM2 | 2022-03-14T09:23:18: PM2 log: App [dadjokes:0] exited with code [0] via signal [SIGINT]
PM2 | 2022-03-14T09:23:18: PM2 log: App [dadjokes:0] will restart in 100ms
PM2 | 2022-03-14T09:23:18: PM2 log: pid=21869 msg=process killed
PM2 | 2022-03-14T09:23:38: PM2 log: [PM2][WORKER] Reset the restart delay, as app dadjokes has been up for more than 30000ms
PM2 | App [dadjokes:0] starting in -fork mode-
PM2 | App [dadjokes:0] online
0|dadjokes | Random Joke server started on port: 3000
0|dadjokes | GET /joke 200 97 - 760.323 ms
0|dadjokes | GET /joke 200 92 - 750.802 ms
0|dadjokes | GET /joke 200 117 - 715.876 ms
To explore all available options for the logs
command and customize its
output, use:
pm2 logs --help
PM2 offers a cluster mode, enabling networked Node.js applications to harness the full power of the host's CPUs without any code changes. This results in launching multiple application instances (based on your machine's CPU count) on the same port, effectively distributing incoming workloads among them and enhancing performance.
Internally, PM2 relies on Node.js's cluster module to spawn child processes that share the server port. Your application should be stateless to maximize the benefits of PM2's cluster mode. This ensures that any reference to past transactions is managed through a shared, stateful medium like a database or cache.
To initiate your application in cluster mode, first remove it from the process
list. Then, specify the number of workers via the instances
option and set the
exec_mode
option to cluster
:
module.exports = {
apps: [
{
. . .
instances: 'max',
exec_mode : 'cluster',
},
],
};
Then, use the start command with the -i
flag:
pm2 start ecosystem.config.js -i max
Here, max
instructs PM2 to spawn as many workers as there are CPU cores. While
you can specify a particular number, it's not recommended to exceed the number
of CPU cores, as it can lead to resource contention and performance issues.
A common practice is to spawn one worker less than the total CPU cores. If your
server has four cores, you'd typically spawn three workers. Achieve this in PM2
by setting instances
to -1
:
module.exports = {
apps: [
{
. . .
instances: -1,
exec_mode : 'cluster',
},
],
};
Starting the application will yield:
[PM2] App [dadjokes] launched (3 instances)
ββββββ¬ββββββββββββββ¬ββββββββββββββ¬ββββββββββ¬ββββββββββ¬βββββββββββ¬βββββββββ¬βββββββ¬ββββββββββββ¬βββββββββββ¬βββββββββββ¬βββββββββββ¬βββββββββββ
β id β name β namespace β version β mode β pid β uptime β βΊ β status β cpu β mem β user β watching β
ββββββΌββββββββββββββΌββββββββββββββΌββββββββββΌββββββββββΌβββββββββββΌβββββββββΌβββββββΌββββββββββββΌβββββββββββΌβββββββββββΌβββββββββββΌβββββββββββ€
β 0 β dadjokes β default β 1.0.0 β cluster β 1000013 β 0s β 0 β online β 0% β 51.9mb β ayo β disabled β
β 1 β dadjokes β default β 1.0.0 β cluster β 1000023 β 0s β 0 β online β 0% β 48.1mb β ayo β disabled β
β 2 β dadjokes β default β 1.0.0 β cluster β 1000035 β 0s β 0 β online β 0% β 40.3mb β ayo β disabled β
ββββββ΄ββββββββββββββ΄ββββββββββββββ΄ββββββββββ΄ββββββββββ΄βββββββββββ΄βββββββββ΄βββββββ΄ββββββββββββ΄βββββββββββ΄βββββββββββ΄βββββββββββ΄βββββββββββ
You'll notice that the application launches in cluster
mode, not fork
as
before. The number of workers corresponds to your machine's CPU cores.
Using cluster mode ensures balanced request handling across workers. The
NODE_APP_INSTANCE
environment variable can distinguish between processes,
handy for operations exclusive to one process.
Modify the /joke
endpoint in your server.js
file as shown below:
app.get('/joke', async (req, res, next) => {
console.log('Request handled by process:', process.env.NODE_APP_INSTANCE);
if (process.env.NODE_APP_INSTANCE === '0') {
console.log('Executing some operation on process 0 only...');
}
try {
const response = await getRandomJoke();
res.json(response);
} catch (err) {
next(err);
}
});
Restart the application through the command below:
pm2 restart ecosystem.server.js
Afterward, send a handful of requests to the /joke
endpoint and view the logs:
pm2 logs dadjokes
0|dadjokes | Request handled by process: 0
0|dadjokes | Executing some operation on process 0 only...
1|dadjokes | Request handled by process: 1
2|dadjokes | Request handled by process: 2
0|dadjokes | Request handled by process: 0
0|dadjokes | Executing some operation on process 0 only...
1|dadjokes | Request handled by process: 1
0|dadjokes | GET /joke 200 106 - 1935.223 ms
2|dadjokes | Request handled by process: 2
0|dadjokes | GET /joke 200 160 - 1137.809 ms
0|dadjokes | Request handled by process: 0
0|dadjokes | Executing some operation on process 0 only...
1|dadjokes | Request handled by process: 1
2|dadjokes | GET /joke 200 171 - 1083.629 ms
2|dadjokes | GET /joke 200 100 - 2079.859 ms
2|dadjokes | Request handled by process: 2
0|dadjokes | Request handled by process: 0
0|dadjokes | Executing some operation on process 0 only...
0|dadjokes | GET /joke 200 119 - 1070.117 ms
2|dadjokes | GET /joke 200 130 - 1073.907 ms
1|dadjokes | GET /joke 200 142 - 6049.548 ms
1|dadjokes | GET /joke 200 111 - 3412.896 ms
1|dadjokes | GET /joke 200 148 - 5938.372 ms
0|dadjokes | GET /joke 200 111 - 5827.303 ms
You'll observe that a number prefixes each log entry. This number is the id
for each worker process, and it can be accessed in the application code via
process.env.NODE_APP_INSTANCE
as demonstrated earlier. Notice how we can
execute some code on process 0
only even though requests were sent to all
three worker instances (on our test machine, could be more or less on yours).
An advantage of cluster mode is zero-downtime reloads in production using
pm2 reload
. This restarts processes sequentially, ensuring continuous
availability. Conversely, pm2 restart
halts and restarts all processes
simultaneously, causing brief downtime.
PM2 also offers dynamic scaling with the scale command. For instance, to add two workers:
pm2 scale dadjokes +2
Or, to set a specific number of workers:
pm2 scale dadjokes 2
This flexibility ensures your application can adapt to varying workloads quickly and efficiently.
PM2 provides an
integrated deployment system
to facilitate deploying your application to one or multiple remote servers. To
set this up, modify your ecosystem.config.js
file as follows:
module.exports = {
apps: [],
deploy: {
production: {
user: '<your_remote_server_username>',
host: [<your_remote_server_ip>],
ref: 'origin/master',
repo: '<your_git_repo_url>',
path: '/home/<your_server_username>/dadjokes',
'post-setup': 'npm install',
'post-deploy': 'pm2 startOrRestart ecosystem.config.js --env production',
},
},
};
Here's a breakdown of the production object properties:
user
: The username for authentication on the remote server.host
: An array of IP addresses of the remote servers.ref
: The git branch and remote to deploy, e.g., origin/master
.repo
: The git repository's remote URL (HTTPS or SSH).path
: The directory on the remote server where the repository will be
cloned.post-setup
: Commands or scripts to run post cloning.post-deploy
: Commands or scripts to run after deployment.Before deploying to the specified host servers, ensure each has PM2 installed and the necessary permissions to clone the Git repository (e.g., the correct SSH key setup). Once set up, initiate the server provisioning with:
pm2 deploy production setup
Here's the output to expect if everything goes well:
--> Deploying to production environment
--> on host <your_remote_server_ip>
β hook pre-setup
β running setup
β cloning https://github.com/betterstack-community/dadjokes
β full fetch
Cloning into '/home/ayo/dadjokes/source'...
β hook post-setup
β setup complete
--> Success
If you encounter an error, it's likely SSH-related, preventing PM2 from
accessing the remote server or Git repository. Start your investigation by
ensuring that the git clone <your_git_repo_url>
command works on the remote
server. For troubleshooting PM2 deployments, refer to this
guide.
After successful provisioning, deploy the application:
pm2 deploy production
pm2 deploy production --force # if there are local changes
A successful deployment will show:
--> Deploying to production environment
--> on host <your_git_repo_url>
β deploying origin/prod
β executing pre-deploy-local
β hook pre-deploy
β fetching updates
β full fetch
Fetching origin
β resetting HEAD to origin/prod
HEAD is now at 4c31583 Add pm2 config file
β executing post-deploy `pm2 startOrRestart ecosystem.config.js --env production`
[PM2][WARN] Applications dadjokes not running, starting...
[PM2][WARN] Environment [production] is not defined in process file
[PM2] App [dadjokes] launched (1 instances)
. . .
--> Success
Logging into your remote server and executing pm2 list
will confirm the
application's deployment and operational status. Additionally, you can run
commands on each remote server without logging in using the pm2 deploy
command. For instance, to reload the dadjokes
application on all remote
servers, run:
pm2 deploy production exec "pm2 reload dadjokes"
A successful reload will display:
--> Deploying to production environment
--> on host <your_remote_server_ip>
Use --update-env to update environment variables
[PM2] Applying action reloadProcessId on app [dadjokes](ids: [ 0 ])
[PM2] [dadjokes](0) β
--> Success
For a deeper dive into deployment commands and hooks, consult the relevant PM2 documentation.
Note that while PM2 provides a
Docker integration
through its pm2-runtime
command, the
general recommendation
is to forgo using PM2 if you're deploying your Node.js application with Docker.
PM2 is a powerful tool for managing and maintaining Node.js applications, offering several features to streamline development and production workflows. While we've covered many of its capabilities in this guide, a vast array of features is still waiting to be explored.
Whether you're looking to integrate PM2 with Docker, run it without a daemon, or set it up with NGINX as a reverse proxy, the possibilities are vast. The PM2 documentation should be your next stop to gain insights into those topics and many others.
Note that the complete source code from this tutorial is available on the prod branch of the dadjokes GitHub repository. It is a practical reference and a starting point for your projects.
Thanks for reading, and happy coding!
Are you a developer and love writing and sharing your knowledge with the world? Join our guest writing program and get paid for writing amazing technical guides. We'll get them to the right readers that will appreciate them.
Write for usWrite a script, app or project on top of Better Stack and share it with the world. Make a public repository and share it with us at our email.
community@betterstack.comor submit a pull request and help us build better products for everyone.
See the full list of amazing projects on github