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-envist beim Neustart erforderlich, um neue Umgebungsvariablen zu übernehmen
- Verwenden Sie
pm2 restart allniemals ohne--update-env, nachdem Sie.env.localaktualisiert haben.watch: falsein der Produktion – die Dateiüberwachung führt zu endlosen Neustartschleifen mit Build-Ausgabenmax_memory_restartbietet automatischen Schutz vor Speicherlecks, ohne den Prozess dauerhaft abzubrechennode_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 selbstpm2 saveundpm2 startupbehalten 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.
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.
Verwandte Artikel
AWS EC2 Deployment Guide for Web Applications
Complete AWS EC2 deployment guide: instance selection, security groups, Node.js deployment, Nginx reverse proxy, SSL, auto-scaling, CloudWatch monitoring, and cost optimization.
Zero-Downtime Database Migrations with Drizzle ORM
Run database migrations without downtime using Drizzle ORM. Covers expand-contract pattern, backward-compatible schema changes, rollback strategies, and CI/CD integration for PostgreSQL.
Next.js 16 App Router: Production Patterns and Pitfalls
Production-ready Next.js 16 App Router patterns: server components, caching strategies, metadata API, error boundaries, and performance pitfalls to avoid.