RunWisp

Comparison · vs PM2

RunWisp vs PM2: process supervision without the Node runtime.

A real ecosystem.config.js next to the equivalent runwisp.toml service. Replicas without Node cluster mode, restart backoff curves, scheduled jobs as their own kind, every restart as a row in history.

Quick start GitHub

Should you switch?

The 10-second version. Skim, decide, scroll down for the receipts.

Switch to RunWisp if

  • Your fleet isn't all Node; you'd rather not ship npm everywhere.
  • You want every restart as a queryable run row, not a tail log.
  • Scheduled jobs deserve their own kind, not cron_restart.
  • Restart backoff should curve, not cap-and-stop.
  • Graceful stop should kill the worker's children too, not just the parent.
  • A Web UI / TUI without a paid hosted tier matters.

Stay on PM2 if

  • Your app needs Node's shared-socket cluster mode today.
  • pm2 reload rolling restarts are load-bearing for deploys.
  • Native Windows hosting is non-negotiable.
  • pm2.io's hosted dashboard with retained metrics is in use.

Yes / no, at a glance

Yes / no, both columns win on something.

Feature PM2 RunWisp
Runtime-free single binary
needs Node
Language-agnostic supervisor
~ Node-flavoured
Declarative config
JS module
validated TOML
Always-on services
Scheduled jobs
~ cron_restart
[tasks.*]
Replicas as one config
Node cluster shared socket
Restart-backoff curve
~ exp_backoff_restart_delay
Duration-based stability reset
max_restarts cap
healthy_after
FATAL after repeated fast-fails
max_restarts → errored
start_retries
Per-run history rows
Web UI in the binary
pm2.io is hosted
TUI
pm2 monit
Process-group-wide graceful stop
Coalesced failure notifications
Zero-downtime rolling reload
pm2 reload
Auto-write init script
pm2 startup
runwisp service install
Native Windows
Linux/macOS/WSL

Pros & cons

What PM2 still does better

  • Node cluster mode with a shared listening socket and master round-robin.
  • pm2 reload rolls workers one at a time; the listening port never drops.
  • npm i -g pm2 in a pure-Node shop is the smaller artifact.
  • Native Windows hosting today.
  • pm2.io's hosted dashboard with retained metrics ships now (paid tier).

What RunWisp adds

  • Single static binary; run = "node app.js" works without Node on the host.
  • Declarative runwisp.toml validated up-front by runwisp validate.
  • instances = N spawns N independent crash domains, not a master fan-out.
  • [tasks.*] and [services.*] split at the schema; cron_restart isn't a substitute.
  • Restart backoff as a curve plus healthy_after; start_retries caps fast-fails before a FATAL stop.
  • Every replica start and exit is a row in the Web UI with exit code and captured streams.
  • Graceful stop sends SIGTERM to the whole process group by default.
  • runwisp service install writes the systemd / launchd unit for the daemon.
  • Web UI and runwisp tui ship in the binary; no paid tier gates.

The simple case

Keep one Node API process alive. Four JS lines vs one TOML stanza.

ecosystem.config.js
ecosystem.config.js
// ecosystem.config.js
module.exports = {
  apps: [
    {
      name:   "api",
      script: "/srv/api/server.js",
    },
  ],
};
runwisp.toml
runwisp.toml
# runwisp.toml
[services.api]
run = "node /srv/api/server.js"

Production-grade

Three workers, hardened. cluster mode + max_memory_restart on the left; independent crash domains and named backoff on the right.

ecosystem.config.js (production)
ecosystem.config.js (production)
// ecosystem.config.js, three workers, production
module.exports = {
  apps: [
    {
      name:          "api-worker",
      script:        "./worker.js",
      instances:     3,
      exec_mode:     "cluster",
      min_uptime:    "10s",
      max_restarts:  10,
      exp_backoff_restart_delay: 100,        // ms
      max_memory_restart:        "512M",
      kill_timeout:              20000,      // SIGTERM grace, ms
      out_file:   "/var/log/api-worker.out.log",
      error_file: "/var/log/api-worker.err.log",
      env_production: {
        NODE_ENV:  "production",
        QUEUE_URL: "redis://localhost:6379",
      },
    },
  ],
};
runwisp.toml (production)
runwisp.toml (production)
# runwisp.toml, three workers, production
[services.api-worker]
description       = "Three Node workers consuming the same job queue"
instances         = 3
restart_delay     = "1s"
restart_backoff   = "exponential"
healthy_after     = "2m"        # how long "up" counts as stable
start_retries     = 10          # fast-fail this many times, then go FATAL
graceful_stop     = "20s"       # finish the in-flight job before SIGKILL
env               = { NODE_ENV = "production", QUEUE_URL = "redis://localhost:6379" }
keep_runs         = 500
notify_on_failure = ["slack-ops"]
run = """
trap 'echo "SIGTERM received, draining"; exit 0' TERM INT
node --max-old-space-size=512 ./worker.js
"""

Migration cheat sheet

ecosystem.config.js field on the left, runwisp.toml field on the right.

PM2 RunWisp
apps[].name [services.<name>] or [tasks.<name>]
script: run =
instances: 3 instances = 3
exec_mode: "cluster" N independent processes; bind with SO_REUSEPORT or proxy in front
min_uptime healthy_after
max_restarts start_retries (then FATAL)
exp_backoff_restart_delay restart_backoff = "exponential"
kill_timeout: 20000 graceful_stop = "20s"
max_memory_restart: "512M" node --max-old-space-size=512 inside run
env_production: { … } env = { … }
cron_restart Model as [tasks.*] with cron =
pm2 logs Per-run log download from Web UI; runwisp tui
pm2 reload (rolling restart) No zero-downtime rolling restart; runwisp reload re-reads config in place
pm2 startup runwisp service install (systemd / launchd)

Gotchas

FAQ

More comparisons: RunWisp vs cron · RunWisp vs supervisord · RunWisp vs systemd timers .

Or see what RunWisp is in one page →

One binary supervising your processes. No Node runtime on the host.

Copy an ecosystem.config.js app into a services stanza, set instances, restart the daemon. Replicas come up indexed and visible, with every restart in run history.