systemd or pm2 for a small Node app on one VPS?

· 2 min read

Both keep a Node process alive. The choice matters less than the details around it, and one detail in particular can make a deploy silently do nothing. This is what I actually ran in clean Debian 12 containers, what I did not, and which I would choose.

What I verified for pm2

It pins the working directory of the first start. With release folders and a current symlink, I switched the symlink and ran pm2 reload app, then pm2 restart app. pm2 kept running the old release, because it keeps the working directory the process had when it was first started. A deliberately broken release was reported healthy, since the old code was still answering. What works is pm2 delete app followed by pm2 start from inside current/.

The cost of that. Deploying that way meant roughly a quarter of requests failed during the deploy (32 of 116 in one run, 31 to 35 failed across several runs, under 20 requests per second through Caddy). Blue/green with two ports and a graceful Caddy reload brought that to zero failed requests in every run. I wrote the measurements up in zero-downtime deploys on one VPS.

What I verified for systemd

  • A unit with Restart=on-failure brought the service back after kill -9.
  • After switching the current symlink, restarting the unit picked up the new release when WorkingDirectory points at current. This is the behaviour pm2 did not give me.
  • systemd-analyze verify was clean on the unit, and MemoryMax was enforced (a process over the limit was killed).
  • An EnvironmentFile with mode 600 kept secrets out of the repository and out of the unit file.
  • The service came back after a container restart (systemd as PID 1).

What I did not verify

  • pm2 cluster mode, pm2's log tooling and pm2 startup scripts on a real reboot.
  • systemd's behaviour on a real machine reboot (only a container restart was tested).
  • Ubuntu-specific behaviour.
  • Any downtime number for a plain systemctl restart of a Node app: it is also a restart, so assume a gap unless you add the blue/green switch in front of it.

My pick, and why

For one app on one server I would use systemd: one fewer moving part (no extra daemon to keep alive and upgrade), the process manager is the same one that manages everything else on the machine, and the symlink-release layout works as you would expect. Reasons to pick pm2 instead are real, though: you already know it, you want its process list and log commands, or you rely on cluster mode (which I did not test). That is an opinion, not a measurement.

Whichever you pick, put zero-downtime switching in front of it (two ports and a reverse proxy) and write the restore test before you need it.

Where the verified parts live

The systemd unit, the symlink deploy and rollback, the memory limit and the rest were all run as scripts in a container, and they ship with the Small App on One VPS handbook, including a command to re-run every check.