PM2 vs systemd for Node.js: Why systemd Wins
Compare PM2 and systemd for a single Ubuntu VPS and see why the init system you already run is the better supervisor for Node.js in production.
Netbay Developer Relations
Netbay Engineering
On this page
PM2 is the first process manager many Node developers meet. It restarts crashed apps, has a pretty process list, and offers a cluster mode that forks your HTTP server across cores. It is also a second supervisor on a machine that already has one. On an Ubuntu VPS you are root, systemd is pid 1, and PM2 startup.sh generates a systemd unit that starts PM2 so PM2 can start Node. That extra hop is the argument. This post picks a side: use systemd as the supervisor and keep PM2 for laptops or for hosts where you do not own init.
The comparison is for one app, maybe a few, on one Ubuntu 24.04 VPS. It is not a cluster orchestrator bake-off. If you need rolling deploys across twelve machines, you are not choosing between pm2 start and systemctl enable.
What both tools actually do
A supervisor starts a process, restarts it when it dies, captures logs, and launches it at boot. systemd does that with a unit file, cgroups, and journald. PM2 does that with a god process, a dump file of the process list, and its own log files under ~/.pm2. Cluster mode in PM2 is Node cluster wrapped in a CLI: several processes bind (or share) a port and PM2 load-balances. systemd can start N identical units with a template nodeapp@.service, or you can run the Node cluster module yourself and still have one unit.
# /etc/systemd/system/nodeapp@.service
[Unit]
Description=Node HTTP worker %i
After=network-online.target
[Service]
Type=simple
User=nodeapp
Group=nodeapp
WorkingDirectory=/srv/app
EnvironmentFile=/etc/nodeapp/app.env
Environment=WORKER_ID=%i
ExecStart=/usr/bin/node /srv/app/server.js
Restart=on-failure
RestartSec=2
[Install]
WantedBy=multi-user.targetsystemctl enable --now nodeapp@1 nodeapp@2 gives you two workers without a god process. nginx can upstream to two loopback ports, or the workers can share a systemd socket. MemoryMax=768M on the unit (or on a slice) is a cgroup cap PM2 cannot enforce without the same systemd underneath.
Where PM2 looks better, and why it fades on a VPS
pm2 start server.js is faster to type than writing a unit. pm2 logs is nicer than journalctl the first time. pm2 monit looks like htop for Node. Those are developer ergonomics. On a server the questions change. Who starts the supervisor after reboot? pm2 startup writes a systemd unit named pm2-root or pm2-ubuntu. You now have systemd watching PM2 watching Node. Logs live in two places. Memory limits are either missing or you add them to the generated unit anyway. Socket activation, ProtectSystem, and NoNewPrivileges are unit fields you will re-implement as PM2 flags or not at all.
// ecosystem.config.js -- extra layer you do not need on Ubuntu
module.exports = {
apps: [{
name: 'nodeapp',
script: '/srv/app/server.js',
instances: 2,
exec_mode: 'cluster',
env: { NODE_ENV: 'production', PORT: '3000' }
}]
};The env block in that file is a secret store in git clothing. systemd EnvironmentFile keeps values out of the repo. Cluster mode also shares a single port via Node cluster; a bug in the master process takes every worker with it. Two systemd template instances fail independently.
Unprivileged PaaS-style accounts are the remaining honest use for PM2. If you cannot write /etc/systemd/system, PM2 as a user daemon is a workaround. On a Netbay VPS you can write unit files. Use that privilege.
Pick one and delete the other
Running both is the failure mode. A leftover pm2 resurrect after boot plus a systemd unit gives you two Node processes fighting on 3000. Grep the host: systemctl list-units --type=service | grep -e node -e pm2 and pm2 list. Uninstall PM2 with npm rm -g pm2 only after pm2 unstartup and pm2 delete all, or the generated unit will fail at boot and page you.
Logs should have one home. If you pick systemd, StandardOutput=journal and you are done. If you pick PM2, you still want those files in journald or you will forget to rotate them. That is another vote for the init system.
Startup time, cgroup accounting, and DDoS-era fd limits are also systemd's job. L3/L4 filtering does not care which Node supervisor you run, but LimitNOFILE and MemoryMax only apply if the process sits in a unit you wrote. A PM2 daemon started from a user session may not even be in the slice you think it is.
The takeaway: PM2 is a good userland supervisor. systemd is the host supervisor. On a VPS you own, writing a unit is less work than running a god process that a unit would have to start anyway. Pick systemd, delete PM2, and spend the leftover attention on the app.
You can compare both on a Netbay Ubuntu 24.04 VPS in Lucknow and keep the unit file — netbayhosts.in.
Keep reading
Follow along on a real VPS
Deploy Linux in under 60 seconds
These guides are written against Ubuntu, Debian, and RHEL-family images — the same ones on NetBay.
Deploy an instance