PM2 & systemd
Supervise Node.js 24 processes on VMs with PM2 or systemd when you are not on Kubernetes or Fargate.
Search across all documentation pages
Supervise Node.js 24 processes on VMs with PM2 or systemd when you are not on Kubernetes or Fargate.
Quick-reference recipe card - copy-paste ready.
# /etc/systemd/system/api.service
[Unit]
Description=Node API
After=network.target
[Service]
Type=simple
User=nodeapp
WorkingDirectory=/opt/api
Environment=NODE_ENV=production
Environment=PORT=3000
ExecStart=/usr/bin/node dist/main.js
Restart=on-failure
RestartSec=5
KillSignal=SIGTERM
TimeoutStopSec=30
[Install]
WantedBy=multi-user.targetWhen to reach for this: Bare EC2, on-prem VMs, or legacy deploy paths. Prefer containers + orchestrator for new services.
[Unit]
Description=Acme API (Node 24)
After=network-online.target
Wants=network-online.target
[Service]
User=nodeapp
Group=nodeapp
WorkingDirectory=/opt/api/current
EnvironmentFile=/etc/acme/api.env
ExecStart=/usr/bin/node --max-old-space-size=512 dist/main.js
Restart=on-failure
RestartSec=5
KillMode=mixed
KillSignal=SIGTERM
TimeoutStopSec=45
LimitNOFILE=65535
[Install]
WantedBy=multi-user.targetsudo systemctl daemon-reload
sudo systemctl enable api
sudo systemctl start api
sudo journalctl -u api -f# Deploy new release
sudo -u nodeapp ln -sfn /opt/api/releases/20260709-abc123 /opt/api/current
sudo systemctl restart api// ecosystem.config.cjs
module.exports = {
apps: [
{
name: "api",
cwd: "/opt/api/current",
script: "dist/main.js",
instances: 2,
exec_mode: "cluster",
env: {
NODE_ENV: "production",
PORT: 3000,
},
max_memory_restart: "600M",
kill_timeout: 30_000,
listen_timeout: 10_000,
merge_logs: true,
log_date_format: "YYYY-MM-DD HH:mm:ss Z",
},
],
};pm2 start ecosystem.config.cjs
pm2 save
pm2 startup systemd -u nodeapp --hp /home/nodeappWhat this demonstrates:
current release directory for atomic deploy switchKillSignal=SIGTERM and kill_timeout align with graceful shutdown| Feature | systemd | PM2 |
|---|---|---|
| Cluster on VM | One unit per instance or manual | Built-in instances |
| Log aggregation | journald | PM2 logs + optional forward |
| Zero-downtime reload | systemd restart blip | pm2 reload cluster |
| OS integration | Native on Linux | Node npm package |
| Best for | Single process, simple ops | Multi-process on one VM |
pm2 reload apiStarts new workers before killing old (similar to rolling update). Requires app to handle SIGINT/SIGTERM - Graceful Shutdown.
# /etc/acme/api.env (chmod 600, root:nodeapp)
NODE_ENV=production
PORT=3000
DATABASE_URL=postgres://...Never world-readable secret files. Prefer SSM agent pulling secrets at boot on AWS VMs.
Internet -> nginx (TLS) -> localhost:3000 (Node)
nginx handles TLS and request buffering; Node binds localhost only if desired.
node process per container.nodeapp user with filesystem ownership.TimeoutStopSec - systemd sends SIGKILL at default 90s inconsistently. Fix: explicit 30-45s matching app shutdown.pm2 startup forgotten after reboot - service down after instance recycle. Fix: pm2 save after startup setup.dist/. Fix: release dirs + ln -sfn swap.| Alternative | Use When | Don't Use When |
|---|---|---|
| systemd | Simple single-process VM | Need built-in cluster on VM |
| PM2 cluster | Multi-core VM without containers | Already on K8s |
| Docker on VM | Container parity without K8s | Minimal container ops appetite |
| ECS/Fargate | Leave VM ops entirely | Hard on-prem requirement |
Default to containers on ECS/Fargate or K8s. PM2 remains valid for legacy VMs and quick EC2 setups.
Match CPU cores (often 2-4 on a small API VM). Measure RSS; avoid OOM from too many heaps.
Rare for Node HTTP servers. Bind port in app with PORT env; nginx in front is simpler.
journald rotates automatically. PM2: pm2 install pm2-logrotate or forward to Loki/Datadog agent.
Use absolute path /usr/bin/node or nvm shim in systemd ExecStart. Document in runbook.
ALB health check to /health or local cron curl alerting. Same endpoints as containers.
Stack versions: This page was written for Node.js 24.18.0 (Active LTS), npm 10+, TypeScript 5.6+, Express 5, Fastify 5, and NestJS 11.
Reviewed by Chris St. John·Last updated Jul 18, 2026