PM2 vs systemd: How to Keep a Node.js App Running on a Server
When you run node app.js over SSH and log out, your app dies. How PM2 and systemd keep it running, restart it after crashes and reboots, handle logs and environment variables — with working configs for both, and where Docker fits.
You SSH into your server, run node server.js, see "Listening on port 3000", close your laptop — and your app goes down. Processes started in a terminal are tied to that terminal session. To run an app properly on a server, you need a process manager: something that starts it in the background, restarts it if it crashes, and starts it again after a reboot.
The two common choices for Node.js are PM2 and systemd.
What any process manager must do
- Run in the background, independent of your SSH session.
- Restart on crash — with a delay, so a crash loop doesn't spin the CPU.
- Start on boot.
- Capture logs somewhere you can read them.
- Provide environment variables (database URL, API keys). (Environment variables explained.)
PM2
PM2 is a process manager written for Node.js, installed with npm.
npm install -g pm2
pm2 start server.js --name myapp
pm2 save # remember the current process list
pm2 startup # prints a command that sets PM2 to start on boot — run it
Everyday commands:
pm2 list # what's running
pm2 logs myapp # tail logs
pm2 restart myapp # restart
pm2 reload myapp # zero-downtime reload (cluster mode)
pm2 monit # live CPU/memory view
For anything beyond one command, use an ecosystem.config.js:
module.exports = {
apps: [
{
name: "myapp",
script: "dist/server.js",
instances: 2, // or "max" — one per CPU core
exec_mode: "cluster",
max_memory_restart: "500M",
env: { NODE_ENV: "production", PORT: "3000" },
},
],
};
pm2 start ecosystem.config.js
Strengths: Node-friendly, quick to learn, cluster mode to use multiple CPU cores and reload without downtime, nice log and monitoring commands, works without admin rights for the app itself.
Weaknesses: another tool to install and keep updated; its boot integration usually relies on systemd underneath anyway; log files need rotating (there's a pm2-logrotate module).
systemd
systemd is the init system built into almost every modern Linux distribution. It already manages every other service on the machine — SSH, Nginx, Postgres — and can manage yours the same way.
Create /etc/systemd/system/myapp.service:
[Unit]
Description=My Node app
After=network.target
[Service]
Type=simple
User=deploy
WorkingDirectory=/home/deploy/myapp
ExecStart=/usr/bin/node dist/server.js
Restart=on-failure
RestartSec=5
Environment=NODE_ENV=production
EnvironmentFile=/home/deploy/myapp/.env
[Install]
WantedBy=multi-user.target
Then:
sudo systemctl daemon-reload
sudo systemctl enable --now myapp # start now and on every boot
sudo systemctl status myapp
journalctl -u myapp -f # follow logs
sudo systemctl restart myapp
Strengths: already installed, no extra dependency, battle-tested, logs go to the system journal (rotated automatically), easy to run the app as a dedicated unprivileged user, and fine-grained security and resource controls (memory limits, sandboxing options). Weaknesses: needs root (sudo) to set up system services; more syntax to learn; no built-in cluster mode — run several instances with a template unit, or let your app use Node's cluster module.
Side by side
| PM2 | systemd | |
|---|---|---|
| Install | npm install -g pm2 |
Built in |
| Start on boot | pm2 startup + pm2 save |
systemctl enable |
| Restart on crash | Yes | Restart=on-failure |
| Multiple instances / zero-downtime reload | Cluster mode built in | Manual |
| Logs | PM2 log files (rotate them) | journald (journalctl) |
| Needs root | Only for boot setup | Yes |
| Non-Node apps | Possible | Anything |
Which should you use?
- One or two Node apps, want simple commands and easy multi-core: PM2.
- You prefer standard Linux tooling, run several kinds of services, or want the tightest security: systemd.
- Either way, run the app as a non-root user. If it's compromised, the damage is contained. (Linux file permissions explained.)
And Docker?
If your app runs in a container, Docker can be the process manager: restart: unless-stopped in Docker Compose restarts it on crash and on boot, and docker logs collects output. Then you don't need PM2 inside the container — one process per container, managed by Docker. (Writing a production Dockerfile.)
Don't forget graceful shutdown
Restarts and deploys send your app a SIGTERM signal. If it exits immediately, in-flight requests fail. Handle the signal, stop accepting new connections and finish current ones. See graceful shutdown in Node.js, and zero-downtime deploys for the bigger picture.
The summary
- Apps started in an SSH session die when it ends; use a process manager.
- PM2: Node-focused, easy commands, cluster mode,
pm2 startup+pm2 savefor boot. - systemd: built into Linux, a unit file with
Restart=on-failure, logs viajournalctl. - Run as a non-root user, and handle
SIGTERMgracefully.
EasySpawn servers are managed: they keep running when you close the browser, system-level services are handled for you, and Claude Code can set up a user-level process manager like PM2 for your app — no root access or unit files needed. See how it works or join the waitlist.
Related: What Is Node.js? · What Is a VPS? · Health Check Endpoints · Structured Logging
Keep reading
VPS vs PaaS: Where Should a Small App Live?
A VPS is cheap and does whatever you tell it — including nothing when it breaks. A PaaS runs your app for you and bills you for the privilege, often by usage. What each one actually includes, what it quietly leaves to you, and how to decide for a side project, an AI-built app, or a small business.
How to Deploy a FastAPI App to Production
Running uvicorn main:app --reload is for development. A production FastAPI deploy needs worker processes, a process manager or container, a reverse proxy with HTTPS, proper settings, migrations, and health checks. Step-by-step options for a VPS and Docker.