← Back to list

Process Manager (PM2) vs systemd: The Mental Model Nobody Explains

TL;DR: It’s not about PM2 vs systemd. It’s about who owns the process — the runtime or the OS. Once you understand that, the “always use…

Khairul Basar · 2026-06-10 09:26 · 0 claps · 4.0 min read
#pm2 #systemd #software-deployment
Open on Medium ↗

Process Manager (PM2) vs systemd: The Mental Model Nobody Explains

TL;DR: It’s not about PM2 vs systemd. It’s about who owns the process — the runtime or the OS. Once you understand that, the “always use PM2 for Node” advice stops being a rule you follow and becomes a conclusion you’d reach yourself.

The Fundamental Problem: Who Babysits Your App?

When you run node server.js in a terminal, three things happen:

  1. The OS spawns a process
  2. Your terminal becomes the parent of that process
  3. When the terminal dies, the process dies with it

This is the core problem every deployment tool is trying to solve: make your app survive beyond the shell that started it, and restart it when it crashes.

There are two completely different philosophies for solving this:

Neither is universally better. They’re answering slightly different questions.

Mental Model 1: PM2 — “A Parent Process That Speaks Node”

Think of PM2 as a persistent supervisor daemon that lives in userspace and speaks the same language as your app.

[Your Server OS]
    └── [PM2 Daemon] ← runs as a regular process, started once
          ├── [Node App: api] ← child process
          ├── [Node App: api] ← child process (cluster mode)
          ├── [Node App: api] ← child process (cluster mode)
          └── [Node App: worker] ← child process

PM2 knows things the OS never will:

  • Node’s event loop — it can detect when your app is “alive” but actually frozen (hanging promises, blocked event loop)
  • Cluster mode — it spawns multiple Node workers and load-balances between them natively, because it understands Node’s cluster API
  • Zero-downtime reload — it can restart workers one by one while traffic keeps flowing
  • Ecosystem filesecosystem.config.js is JavaScript, so your deployment config can have logic in it

The Key Trade-off

PM2 runs as a user process. It starts after login (or via a systemd stub, ironically). It’s one layer removed from the OS boot sequence.

This means:

  • ✅ Faster iteration — pm2 reload, pm2 logs, pm2 monit are developer-friendly
  • ✅ Runtime-specific superpowers (cluster, hot reload)
  • ⚠️ PM2 itself can crash — then all your apps go down
  • ⚠️ You’re adding a middleman to your process tree

Mental Model 2: systemd — “The OS as the Supervisor”

systemd is PID 1 — the very first process the Linux kernel spawns. Everything else on the machine is a child of systemd. When you register your app as a systemd service, you’re telling the OS:

“This is a first-class citizen of this machine. Start it at boot, restart it if it dies, log it to the journal.”

[Linux Kernel]
    └── [systemd] ← PID 1, always running
          ├── [nginx.service]
          ├── [postgresql.service]
          └── [myapp.service] ← your app, same tier as nginx

systemd doesn’t know or care that your app is Python, Go, or Java. It just supervises a process. The .service file is a declaration:

[Unit]
Description=My Python API
After=network.target

[Service]
ExecStart=/usr/bin/python3 /opt/myapp/app.py
Restart=always
User=myapp
WorkingDirectory=/opt/myapp
[Install]
WantedBy=multi-user.target

The Key Trade-off

systemd is the most reliable supervisor possible — it is the OS.

  • ✅ Starts before anything else, even before your user session exists
  • ✅ Guaranteed restart — if systemd is dead, the whole machine is dead anyway
  • ✅ Native integration — journalctl, socket activation, resource cgroups, security sandboxing
  • ✅ Language agnostic — same tool for Python, Go, Java, Rust
  • ⚠️ No runtime superpowers — it can’t do Node clustering or hot module reload
  • ⚠️ Slightly more ceremony to configure (but it’s a one-time cost)

The Real Reason People Say “Use PM2 for Node”

Node has one specific property that makes a process manager compelling: it’s single-threaded by default.

A 4-core server running a single Node process uses 25% of its CPU capacity. To use all 4 cores, you need 4 Node processes — but then you need something to:

  1. Spawn all 4 workers
  2. Route incoming connections between them
  3. Restart individual workers without dropping traffic

That’s PM2’s cluster mode. systemd cannot do this. systemd can restart a crashed process, but it can’t orchestrate intra-process load balancing.

So the real rule is:

Use a process manager when you need runtime-specific orchestration (clustering, hot reload). Use systemd when you need OS-level reliability and don’t need those features.

For Python/Go/Java, you typically solve concurrency differently:

  • Python: Gunicorn spawns workers internally, then you put Gunicorn under systemd
  • Go: it’s multi-threaded natively, single process is fine → straight to systemd
  • Java: JVM handles threads internally → straight to systemd

The Pattern That Reconciles Both

The most production-grade pattern isn’t PM2 or systemd — it’s PM2 under systemd:

[systemd]
    └── [pm2.service] ← systemd ensures PM2 itself always runs
          ├── [node api worker 1]
          ├── [node api worker 2]
          └── [node api worker 3]

You get:

  • systemd’s boot-time reliability and automatic recovery
  • PM2’s cluster management and zero-downtime reloads

PM2 even generates this config for you: pm2 startup systemd

Decision Framework

Is your app Node.js?
├── Yes → Do you need multi-core clustering or zero-downtime reload?
│         ├── Yes → Use PM2 (optionally backed by systemd)
│         └── No  → systemd is perfectly fine
└── No (Python, Go, Java, Rust…)
      → Does your runtime handle its own concurrency internally?
        ├── Yes (Go, Java, .NET) → systemd directly
        └── No (Python WSGI)    → Use a runtime worker manager (Gunicorn, uWSGI)
                                   then put *that* under systemd

The One-Sentence Mental Model

systemd owns the machine; a process manager owns the runtime.

Use the one that owns the thing you actually need supervised. For most production apps, the answer is: let systemd own the process manager, and let the process manager own your app workers.

Written to demystify deployment fundamentals — not to prescribe a single “right” answer.


메타데이터
post_id
237a89a2f001
slug
process-manager-pm2-vs-systemd-the-mental-model-nobody-explains-237a89a2f001
url
https://medium.com/@khairulrucse26/process-manager-pm2-vs-systemd-the-mental-model-nobody-explains-237a89a2f001
canonical_url
https://medium.com/@khairulrucse26/process-manager-pm2-vs-systemd-the-mental-model-nobody-explains-237a89a2f001
author_url
https://medium.com/@khairulrucse26
status
ok
fetched_at
2026-06-13 00:08:42