PM2 Process Management for Node.js in Production

PM2 process management for Node.js production: ecosystem file configuration, zero-downtime restarts, log management, monitoring, clustering, and deployment workflows.

E
ECOSIRE Research and Development Team
|19. März 20269 Min. Lesezeit1.9k Wörter|

PM2-Prozessmanagement für Node.js in der Produktion

Wenn Ihre Node.js-Anwendung um 2 Uhr morgens abstürzt, ist PM2 der Unterschied zwischen dem automatischen Neustart und dem Anblick einer leeren Seite für Ihre Benutzer, bis Sie aufwachen. PM2 ist ein kampferprobter Prozessmanager, der automatische Neustarts, Clustering für Multi-Core-Nutzung, Protokollaggregation und Bereitstellungen ohne Ausfallzeiten übernimmt – alles mit einer einzigen Konfigurationsdatei, die in Ihrem Repository gespeichert ist.

Dieser Leitfaden behandelt ein Produktions-PM2-Setup, das 5 Node.js-Prozesse gleichzeitig verwaltet: Next.js (Frontend), NestJS (API), Docusaurus (Dokumente) und zwei Markenseiten. Die Muster gelten gleichermaßen für Einzelprozessbereitstellungen.

Wichtige Erkenntnisse

– Die Datei ecosystem.config.cjs (CommonJS, nicht .js) funktioniert sowohl mit ESModule- als auch mit CommonJS-Projekten – Das Flag --update-env ist beim Neustart erforderlich, um neue Umgebungsvariablen zu übernehmen

  • Verwenden Sie pm2 restart all niemals ohne --update-env, nachdem Sie .env.local aktualisiert haben.
  • watch: false in der Produktion – die Dateiüberwachung führt zu endlosen Neustartschleifen mit Build-Ausgaben
  • max_memory_restart bietet automatischen Schutz vor Speicherlecks, ohne den Prozess dauerhaft abzubrechen
  • node_args: '--max-old-space-size=4096' verhindert OOM-Abstürze bei speicherintensiven Vorgängen
  • PM2-Protokolle rotieren mit dem Modul pm2-logrotate – installieren Sie es direkt nach PM2 selbst
  • pm2 save und pm2 startup behalten Ihre Prozessliste über Serverneustarts hinweg bei

Installation

# Install PM2 globally
npm install -g pm2

# Install the log rotation module immediately
pm2 install pm2-logrotate

# Configure log rotation
pm2 set pm2-logrotate:max_size 50M
pm2 set pm2-logrotate:retain 7
pm2 set pm2-logrotate:compress true
pm2 set pm2-logrotate:dateFormat YYYY-MM-DD

Ökosystem-Konfigurationsdatei

Die Datei ecosystem.config.cjs (CommonJS-Format für die Arbeit mit ESM- und CJS-Projekten) definiert alle Ihre Prozesse:

// ecosystem.config.cjs
module.exports = {
  apps: [
    // ─── Next.js Frontend ────────────────────────────────────────────
    {
      name: 'ecosire-web',
      script: 'node_modules/.bin/next',
      args: 'start',
      cwd: '/opt/ecosire/app/apps/web',
      instances: 1,        // Single instance — Next.js handles its own multi-threading
      exec_mode: 'fork',
      env: {
        NODE_ENV: 'production',
        PORT: 3000,
      },
      // Memory management
      max_memory_restart: '1G',
      node_args: '--max-old-space-size=1024',
      // Logging
      out_file: '/var/log/pm2/ecosire-web.out.log',
      error_file: '/var/log/pm2/ecosire-web.err.log',
      merge_logs: true,
      log_date_format: 'YYYY-MM-DD HH:mm:ss Z',
      // Restart behavior
      watch: false,
      restart_delay: 3000,
      max_restarts: 10,
      min_uptime: '30s',   // Must stay up 30s to count as successful start
      autorestart: true,
      // Graceful shutdown
      kill_timeout: 30000, // 30 seconds to shut down gracefully
      wait_ready: true,    // Wait for process.send('ready')
      listen_timeout: 60000,
    },

    // ─── NestJS API ──────────────────────────────────────────────────
    {
      name: 'ecosire-api',
      script: 'dist/main.js',
      cwd: '/opt/ecosire/app/apps/api',
      instances: 2,         // Cluster mode for multi-core utilization
      exec_mode: 'cluster',
      env: {
        NODE_ENV: 'production',
        PORT: 3001,
      },
      max_memory_restart: '512M',
      node_args: '--max-old-space-size=512',
      out_file: '/var/log/pm2/ecosire-api.out.log',
      error_file: '/var/log/pm2/ecosire-api.err.log',
      merge_logs: true,
      log_date_format: 'YYYY-MM-DD HH:mm:ss Z',
      watch: false,
      restart_delay: 2000,
      max_restarts: 10,
      min_uptime: '20s',
      autorestart: true,
      kill_timeout: 15000,
      // Graceful cluster reload support
      listen_timeout: 30000,
    },

    // ─── Docusaurus Docs ─────────────────────────────────────────────
    {
      name: 'ecosire-docs',
      script: 'node_modules/.bin/docusaurus',
      args: 'serve',
      cwd: '/opt/ecosire/app/apps/docs',
      instances: 1,
      exec_mode: 'fork',
      env: {
        NODE_ENV: 'production',
        PORT: 3002,
      },
      max_memory_restart: '256M',
      out_file: '/var/log/pm2/ecosire-docs.out.log',
      error_file: '/var/log/pm2/ecosire-docs.err.log',
      merge_logs: true,
      log_date_format: 'YYYY-MM-DD HH:mm:ss Z',
      watch: false,
      restart_delay: 3000,
      max_restarts: 5,
      min_uptime: '30s',
      autorestart: true,
      kill_timeout: 10000,
    },

    // ─── Brand Site: Odovation ───────────────────────────────────────
    {
      name: 'odovation-web',
      script: 'node_modules/.bin/next',
      args: 'start',
      cwd: '/opt/ecosire/app/apps/odovation',
      instances: 1,
      exec_mode: 'fork',
      env: {
        NODE_ENV: 'production',
        PORT: 3010,
      },
      max_memory_restart: '512M',
      out_file: '/var/log/pm2/odovation-web.out.log',
      error_file: '/var/log/pm2/odovation-web.err.log',
      merge_logs: true,
      log_date_format: 'YYYY-MM-DD HH:mm:ss Z',
      watch: false,
      restart_delay: 3000,
      max_restarts: 10,
      min_uptime: '30s',
      autorestart: true,
    },

    // ─── Brand Site: MuhammadAmir ────────────────────────────────────
    {
      name: 'muhammadamir-web',
      script: 'node_modules/.bin/next',
      args: 'start',
      cwd: '/opt/ecosire/app/apps/muhammadamir',
      instances: 1,
      exec_mode: 'fork',
      env: {
        NODE_ENV: 'production',
        PORT: 3020,
      },
      max_memory_restart: '512M',
      out_file: '/var/log/pm2/muhammadamir-web.out.log',
      error_file: '/var/log/pm2/muhammadamir-web.err.log',
      merge_logs: true,
      log_date_format: 'YYYY-MM-DD HH:mm:ss Z',
      watch: false,
      restart_delay: 3000,
      max_restarts: 10,
      min_uptime: '30s',
      autorestart: true,
    },
  ],
};

Kern-PM2-Befehle

# Start all processes from ecosystem file
pm2 start ecosystem.config.cjs

# Restart all (with updated environment variables)
pm2 restart ecosystem.config.cjs --update-env

# Graceful reload (zero-downtime for cluster mode)
pm2 reload ecosystem.config.cjs

# Stop all processes
pm2 stop all

# Delete all processes from PM2 registry
pm2 delete all

# Individual process management
pm2 restart ecosire-api
pm2 stop ecosire-docs
pm2 logs ecosire-web --lines 100

# Real-time monitoring dashboard
pm2 monit

# Status overview
pm2 status
pm2 list

Start beim Neustart des Servers

Ohne Startkonfiguration gehen beim Neustart des Servers alle PM2-Prozesse verloren:

# Generate and install the startup script for your init system
pm2 startup
# Copy the output command and run it (it looks like:)
# sudo env PATH=$PATH:/usr/bin pm2 startup systemd -u ubuntu --hp /home/ubuntu

# Save the current process list
pm2 save
# This creates ~/.pm2/dump.pm2 — processes are restored on reboot

# Verify startup works
pm2 resurrect  # Manually restore from dump.pm2

Führen Sie jedes Mal, wenn Sie Prozesse hinzufügen oder entfernen, pm2 save erneut aus, um die Dump-Datei zu aktualisieren.


Bereitstellungen ohne Ausfallzeiten

Für NestJS im Cluster-Modus unterstützt PM2 echte Neuladevorgänge ohne Ausfallzeiten:

# Reload restarts workers one at a time (zero-downtime)
# Old workers handle requests while new ones start
pm2 reload ecosire-api

# vs restart — kills all workers simultaneously (brief downtime)
pm2 restart ecosire-api

Für Next.js (das im Fork-Modus, Einzelinstanz läuft), erfordert Null-Ausfallzeit einen anderen Ansatz. Verwenden Sie die Konfiguration wait_ready + listen_timeout mit einem Startsignal von Ihrer App:

// apps/web — this is handled automatically by Next.js
// But for NestJS, send the ready signal explicitly:

// apps/api/src/main.ts
async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  await app.listen(3001);

  // Signal PM2 that the process is ready
  if (process.send) {
    process.send('ready');
  }
}

bootstrap();

Protokollverwaltung

PM2-Protokolle können Ihre Festplatte füllen, wenn sie nicht verwaltet werden. Protokollrotation sofort konfigurieren:

# Install log rotation module
pm2 install pm2-logrotate

# Configuration
pm2 set pm2-logrotate:max_size 50M       # Rotate when log reaches 50MB
pm2 set pm2-logrotate:retain 7           # Keep 7 days of logs
pm2 set pm2-logrotate:compress true      # Gzip rotated logs
pm2 set pm2-logrotate:dateFormat YYYY-MM-DD
pm2 set pm2-logrotate:workerInterval 30  # Check rotation interval (seconds)
pm2 set pm2-logrotate:rotateInterval '0 0 * * *'  # Daily at midnight

Nützliche Protokollbefehle:

# View all logs combined
pm2 logs

# View specific process logs
pm2 logs ecosire-api

# View with timestamps
pm2 logs --timestamp

# Flush all log files
pm2 flush

# Tail error logs only
pm2 logs ecosire-api --err --lines 200

Überwachung und Metriken

PM2 Plus (ehemals Keymetrics) bietet cloudbasiertes Monitoring. Für selbst gehostete Überwachung:

# Built-in terminal dashboard
pm2 monit

# Get JSON status for scripting/monitoring integration
pm2 jlist    # JSON process list
pm2 prettylist  # Formatted process list

# Integrate with your monitoring stack
pm2 set pm2-server-monit:interval 5  # Metrics collection interval

Stellen Sie zur Produktionsüberwachung PM2-Metriken Prometheus zur Verfügung:

npm install -g pm2-prometheus-exporter
pm2 set pm2-prometheus-exporter:port 9209

# Scrape in Prometheus config:
# - job_name: pm2
#   static_configs:
#     - targets: ['localhost:9209']

Bereitstellungsskript-Integration

Eine typische Bereitstellungssequenz:

#!/bin/bash
# scripts/deploy-production.sh

set -e

echo "=== Starting deployment ==="

# 1. Pull latest code
git pull origin main

# 2. Install dependencies
pnpm install --frozen-lockfile

# 3. Build all apps (with Turbo remote cache)
TURBO_TOKEN="$TURBO_TOKEN" TURBO_TEAM="$TURBO_TEAM" \
  npx turbo run build

# 4. Run database migrations
pnpm --filter @ecosire/db db:migrate

# 5. Restart PM2 processes
# --update-env picks up changes in .env.local
pm2 restart ecosystem.config.cjs --update-env

# 6. Wait for processes to stabilize
sleep 10

# 7. Health checks
curl -f https://ecosire.com/ -o /dev/null -s || {
  echo "Web health check failed — rolling back"
  git revert HEAD --no-edit
  pm2 restart ecosystem.config.cjs --update-env
  exit 1
}

curl -f https://api.ecosire.com/api/health -o /dev/null -s || {
  echo "API health check failed — rolling back"
  git revert HEAD --no-edit
  pm2 restart ecosystem.config.cjs --update-env
  exit 1
}

# 8. Save process state
pm2 save

echo "=== Deployment complete ==="

Häufige Fallstricke und Lösungen

Falle 1: --update-env vergessen

Nach der Aktualisierung von .env.local führt die Ausführung von pm2 restart all ohne --update-env dazu, dass Prozesse mit den alten Umgebungsvariablen neu gestartet werden. Verwenden Sie immer pm2 restart ecosystem.config.cjs --update-env.

Falle 2: Verwendung von watch: true in der Produktion

watch: true startet den Prozess neu, wenn sich eine Datei ändert. In der Produktion ändern sich die Build-Ausgaben bei jeder Bereitstellung – dies führt zu endlosen Neustartschleifen. Stellen Sie immer watch: false ein.

Falle 3: SIGTERM wird für ein ordnungsgemäßes Herunterfahren nicht verarbeitet

PM2 sendet SIGTERM beim Neustart/Stoppen. Wenn Ihre App damit nicht zurechtkommt, wartet PM2 kill_timeout Millisekunden und sendet SIGKILL – was zu verlorenen Anfragen führen kann. Behandeln Sie SIGTERM in NestJS:

// main.ts
const app = await NestFactory.create(AppModule);
await app.listen(3001);

// Graceful shutdown
process.on('SIGTERM', async () => {
  await app.close();
  process.exit(0);
});

Falle 4: Der Speicherplatz für PM2-Protokolle geht zur Neige

Ohne pm2-logrotate wachsen PM2-Protokolle auf unbestimmte Zeit. Eine stark frequentierte API kann pro Tag Gigabyte an Protokollen generieren. Installieren Sie pm2-logrotate sofort und legen Sie einen angemessenen max_size (50 MB) und retain (7 Tage) fest.

Falle 5: Verlust von Prozessen nach dem Neustart

pm2 start behält Prozesse nicht über Neustarts hinweg bei. Führen Sie nach der Ersteinrichtung immer pm2 startup + pm2 save aus. Wenn Prozesse nach einem Neustart verschwinden, führen Sie pm2 resurrect aus, um den gespeicherten Speicherauszug wiederherzustellen.


Häufig gestellte Fragen

Wann sollte ich den Cluster-Modus im Vergleich zum Fork-Modus verwenden?

Verwenden Sie den Clustermodus für CPU-gebundene Arbeitslasten (NestJS-APIs mit hohem Rechenaufwand und Datenverarbeitung). Der Cluster-Modus erzeugt instances-Arbeitsprozesse und PM2-Lastausgleiche zwischen ihnen – wobei alle CPU-Kerne genutzt werden. Verwenden Sie den Fork-Modus für E/A-gebundene Arbeitslasten (Next.js, statische Dateibereitstellung) oder wenn der Prozess kein Clustering unterstützt (Single-Thread-Skripte, Docusaurus-Serve). Next.js verarbeitet seine eigenen Arbeitsthreads intern, daher ist der Fork-Modus mit instances: 1 korrekt.

Wie führe ich PM2 in einem Docker-Container aus?

PM2 in Docker verwendet pm2-docker (oder pm2-runtime) anstelle von pm2, um Signale korrekt zu verarbeiten. Die Laufzeitversion führt keine Daemonisierung durch (was dazu führen würde, dass Docker beendet wird), leitet Signale ordnungsgemäß an untergeordnete Prozesse weiter und protokolliert nicht in Dateien, sondern in stdout/stderr. Verwenden Sie CMD ["pm2-runtime", "ecosystem.config.cjs"] in Ihrer Docker-Datei.

Wie überwache ich PM2-Prozesse von einem Remote-Computer aus?

PM2 Plus (Pay-per-Process-Cloud-Dienst) bietet ein Web-Dashboard. Stellen Sie für die selbstgehostete Überwachung die PM2-Metriken über den Prometheus-Exporter bereit und visualisieren Sie sie in Grafana. Für einfache Statusprüfungen können Sie SSH verwenden und pm2 status ausführen oder die Metriken über einen HTTP-Endpunkt verfügbar machen, den Ihr Überwachungssystem abfragt.

Was ist der Unterschied zwischen pm2 reload und pm2 restart?

pm2 restart beendet alle Worker gleichzeitig und startet sie neu – es gibt einen kurzen Zeitraum, in dem keine Worker ausgeführt werden (Ausfallzeit). pm2 reload ist elegant: Es startet neue Worker, wartet darauf, dass sie bereit sind, und fährt dann alte Worker herunter – keine Ausfallzeit. Verwenden Sie pm2 reload für Produktionsbereitstellungen. Hinweis: Das Neuladen funktioniert nur im Cluster-Modus korrekt. Der Fork-Modus greift auf das Neustartverhalten zurück.

Wie lege ich verschiedene Umgebungsvariablen für verschiedene Prozesse fest?

Jeder Prozess in ecosystem.config.cjs hat seine eigenen Abschnitte env und env_production. Der Abschnitt env_production wird verwendet, wenn Sie --env production an PM2-Befehle übergeben. Legen Sie Geheimnisse niemals direkt in der Ökosystemdatei ab, sondern legen Sie sie in der Systemumgebung oder in der .env.local-Datei fest und lassen Sie sie von PM2 erben. Das Flag --update-env stellt sicher, dass PM2 Umgebungsvariablen beim Neustart erneut liest.


Nächste Schritte

PM2 ist ein grundlegender Bestandteil jeder Node.js-Produktionsbereitstellung. ECOSIRE verwaltet 5 PM2-Prozesse in der Produktion – Next.js, NestJS, Docusaurus und zwei Markenseiten – mit automatischen Neustarts, Protokollrotation und Bereitstellungen ohne Ausfallzeiten bei jedem Push auf den Hauptserver.

Ganz gleich, ob Sie DevOps-Engineering-Unterstützung, eine Produktionsbereitstellungsarchitektur oder Hilfe bei der Migration zu einem Container-Setup benötigen, [entdecken Sie unsere Dienste] (/services), um zu erfahren, wie wir Ihnen helfen können.

E

Geschrieben von

ECOSIRE Research and Development Team

Entwicklung von Enterprise-Digitalprodukten bei ECOSIRE. Einblicke in Odoo-Integrationen, E-Commerce-Automatisierung und KI-gestützte Geschäftslösungen.

Chatten Sie auf WhatsApp