運用環境における Node.js の PM2 プロセス管理
Node.js アプリケーションが午前 2 時にクラッシュした場合、PM2 はアプリケーションが自動的に再起動するか、ユーザーが目を覚ますまで空白のページを表示するかの違いです。 PM2 は、自動再起動、マルチコア利用のためのクラスタリング、ログ集約、ダウンタイムなしのデプロイをすべてリポジトリ内に存在する 1 つの構成ファイルで処理する、十分にテストされたプロセス マネージャーです。
このガイドでは、Next.js (フロントエンド)、NestJS (API)、Docusaurus (ドキュメント)、および 2 つのブランド サイトの 5 つの Node.js プロセスを同時に管理する運用 PM2 セットアップについて説明します。このパターンは、単一プロセスの展開にも同様に適用されます。
重要なポイント
ecosystem.config.cjsファイル (.jsではなく CommonJS) は ESModule プロジェクトと CommonJS プロジェクトの両方で動作します- 新しい環境変数を取得するために再起動する場合は
--update-envフラグが必要です.env.localを更新した後は、--update-envなしでpm2 restart allを使用しないでください。watch: false運用環境 — ファイル監視により、ビルド出力で無限の再起動ループが発生するmax_memory_restartは、プロセスを永続的に強制終了することなく、自動メモリ リーク保護を提供しますnode_args: '--max-old-space-size=4096'は、メモリを大量に使用する操作での OOM クラッシュを防止します- PM2 ログは
pm2-logrotateモジュールでローテーションします — PM2 自体の直後にインストールしますpm2 saveとpm2 startupはサーバーの再起動後もプロセス リストを保持します
インストール
# 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
エコシステム構成ファイル
ecosystem.config.cjs ファイル (ESM プロジェクトと CJS プロジェクトの両方で動作する CommonJS 形式) は、すべてのプロセスを定義します。
// 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,
},
],
};
コア PM2 コマンド
# 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
サーバーの再起動時の起動
スタートアップ設定がないと、サーバーの再起動時にすべての PM2 プロセスが失われます。
# 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
プロセスを追加または削除するたびに、pm2 save を再度実行してダンプ ファイルを更新します。
ダウンタイムゼロの導入
クラスター モードの NestJS の場合、PM2 は真のゼロ ダウンタイム リロードをサポートします。
# 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
Next.js (フォーク モード、単一インスタンスで実行される) の場合、ゼロダウンタイムには別のアプローチが必要です。アプリからの起動信号で wait_ready + listen_timeout 構成を使用します。
// 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();
ログ管理
PM2 ログが管理されていない場合、ディスクがいっぱいになる可能性があります。ログ ローテーションをすぐに構成します。
# 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
便利なログコマンド:
# 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
モニタリングとメトリクス
PM2 Plus (旧 Keymetrics) はクラウドベースのモニタリングを提供します。セルフホスト型モニタリングの場合:
# 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
本番環境を監視するには、PM2 メトリクスを Prometheus に公開します。
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']
デプロイメントスクリプトの統合
一般的な展開シーケンスは次のとおりです。
#!/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 ==="
よくある落とし穴と解決策
落とし穴 1: --update-env を忘れる
.env.local を更新した後、--update-env を使用せずに pm2 restart all を実行すると、プロセスが古い環境変数で再起動されます。常に pm2 restart ecosystem.config.cjs --update-env を使用してください。
落とし穴 2: 運用環境での watch: true の使用
watch: true は、ファイルが変更されるとプロセスを再起動します。運用環境では、ビルド出力はデプロイごとに変更されます。これにより、無限の再起動ループが発生します。常にwatch: falseを設定してください。
落とし穴 3: 正常なシャットダウンのために SIGTERM を処理しない
PM2 は再起動/停止時に SIGTERM を送信します。アプリがこれを処理しない場合、PM2 は kill_timeout ミリ秒待って SIGKILL を送信します。これにより、リクエストが失われる可能性があります。 NestJS で SIGTERM を処理します。
// main.ts
const app = await NestFactory.create(AppModule);
await app.listen(3001);
// Graceful shutdown
process.on('SIGTERM', async () => {
await app.close();
process.exit(0);
});
落とし穴 4: PM2 ログのディスク容量不足
pm2-logrotate がないと、PM2 ログは無制限に増大します。トラフィックの多い API では、1 日に数ギガバイトのログが生成されることがあります。 pm2-logrotate をすぐにインストールし、適切な max_size (50MB) と retain (7 日) を設定します。
落とし穴 5: 再起動後にプロセスが失われる
pm2 start は再起動してもプロセスを保持しません。初期セットアップ後は、必ず pm2 startup + pm2 save を実行してください。再起動後にプロセスが消えた場合は、pm2 resurrect を実行して、保存されたダンプから復元します。
よくある質問
クラスター モードとフォーク モードのどちらを使用すべきか?
CPU バウンドのワークロード (大量の計算やデータ処理を伴う NestJS API) にはクラスター モードを使用します。クラスター モードでは、instances ワーカー プロセスが生成され、PM2 はすべての CPU コアを利用してそれらの間で負荷分散を行います。 I/O バウンドのワークロード (Next.js、静的ファイル サービス)、またはプロセスがクラスタリングをサポートしていない場合 (シングル スレッド スクリプト、Docusaurus サービス)、フォーク モードを使用します。 Next.js は独自のワーカー スレッドを内部で処理するため、instances: 1 を使用したフォーク モードは正しいです。
Docker コンテナで PM2 を実行するにはどうすればよいですか?
Docker の PM2 は、信号を正しく処理するために pm2 の代わりに pm2-docker (または pm2-runtime) を使用します。ランタイム バージョンはデーモン化せず (Docker が終了する可能性があります)、シグナルを子プロセスに適切に転送し、ファイルの代わりに stdout/stderr にログを記録します。 Dockerfile で CMD ["pm2-runtime", "ecosystem.config.cjs"] を使用します。
リモート マシンから PM2 プロセスを監視するにはどうすればよいですか?
PM2 Plus(プロセス課金型クラウドサービス)は、Webダッシュボードを提供します。セルフホスト型モニタリングの場合は、Prometheus エクスポーターを介して PM2 のメトリクスを公開し、Grafana で視覚化します。単純なステータス チェックの場合は、SSH で pm2 status を実行するか、監視システムがポーリングする HTTP エンドポイント経由でメトリクスを公開できます。
pm2 リロードと pm2 リスタートの違いは何ですか?
pm2 restart はすべてのワーカーを同時に強制終了し、再起動します。短時間、ワーカーが実行されない状態になります (ダウンタイム)。 pm2 reload は優雅です。新しいワーカーを開始し、準備が完了するのを待ってから、古いワーカーをシャットダウンします。ダウンタイムはゼロです。運用環境の展開には pm2 reload を使用します。注: リロードはクラスター モードでのみ正しく機能します。 fork モードは再起動動作に戻ります。
プロセスごとに異なる環境変数を設定するにはどうすればよいですか?
ecosystem.config.cjs の各プロセスには、独自の env セクションと env_production セクションがあります。 env_production セクションは、--env production を PM2 コマンドに渡すときに使用されます。シークレットについては、決してエコシステム ファイルに直接置かないでください。システム環境または .env.local ファイルに設定して、PM2 に継承させます。 --update-env フラグにより、PM2 は再起動時に環境変数を再読み取りします。
次のステップ
PM2 は、本番環境の Node.js デプロイメントの基本的な部分です。 ECOSIRE は、本番環境の 5 つの PM2 プロセス (Next.js、NestJS、Docusaurus、および 2 つのブランド サイト) を管理し、メインへのプッシュごとに自動再起動、ログ ローテーション、ダウンタイムなしのデプロイメントを実行します。
DevOps エンジニアリング サポートが必要な場合でも、運用環境のデプロイメント アーキテクチャが必要な場合でも、コンテナ化されたセットアップへの移行のサポートが必要な場合でも、当社のサービスをご覧ください して、当社がどのようにお手伝いできるかをご確認ください。
執筆者
ECOSIRE Research and Development Team
ECOSIREでエンタープライズグレードのデジタル製品を開発。Odoo統合、eコマース自動化、AI搭載ビジネスソリューションに関するインサイトを共有しています。
関連記事
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.