Gestión de procesos PM2 para Node.js en producción
Cuando su aplicación Node.js falla a las 2 a.m., PM2 es la diferencia entre que se reinicie automáticamente y que sus usuarios vean una página en blanco hasta que usted se despierte. PM2 es un administrador de procesos probado en batalla que maneja reinicios automáticos, agrupación en clústeres para utilización de múltiples núcleos, agregación de registros e implementaciones sin tiempo de inactividad, todo con un único archivo de configuración que reside en su repositorio.
Esta guía cubre una configuración de producción de PM2 que administra 5 procesos de Node.js simultáneamente: Next.js (frontend), NestJS (API), Docusaurus (docs) y dos sitios de marca. Los patrones se aplican igualmente a implementaciones de proceso único.
Conclusiones clave
- El archivo
ecosystem.config.cjs(CommonJS, no.js) funciona con proyectos ESModule y CommonJS- Se requiere el indicador
--update-enval reiniciar para seleccionar nuevas variables de entorno- Nunca use
pm2 restart allsin--update-envdespués de actualizar.env.localwatch: falseen producción: la observación de archivos provoca infinitos bucles de reinicio con resultados de compilaciónmax_memory_restartproporciona protección automática contra fugas de memoria sin interrumpir el proceso de forma permanentenode_args: '--max-old-space-size=4096'evita fallos de OOM en operaciones con uso intensivo de memoria- Los registros de PM2 rotan con el módulo
pm2-logrotate; instálelo inmediatamente después del propio PM2pm2 saveypm2 startupconservan su lista de procesos a través de reinicios del servidor
Instalación
# 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
Archivo de configuración del ecosistema
El archivo ecosystem.config.cjs (formato CommonJS para trabajar con proyectos ESM y CJS) define todos sus procesos:
// 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,
},
],
};
Comandos principales de 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
Inicio al reiniciar el servidor
Sin la configuración de inicio, todos los procesos PM2 se pierden al reiniciar el servidor:
# 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
Cada vez que agregue o elimine procesos, ejecute pm2 save nuevamente para actualizar el archivo de volcado.
Implementaciones sin tiempo de inactividad
Para NestJS en modo clúster, PM2 admite recargas reales sin tiempo de inactividad:
# 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
Para Next.js (que se ejecuta en modo bifurcación, instancia única), el tiempo de inactividad cero requiere un enfoque diferente. Utilice la configuración wait_ready + listen_timeout con una señal de inicio de su aplicación:
// 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();
Gestión de registros
Los registros de PM2 pueden llenar su disco si no se administran. Configure la rotación de registros inmediatamente:
# 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
Comandos de registro útiles:
# 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
Monitoreo y Métricas
PM2 Plus (anteriormente Keymetrics) proporciona monitoreo basado en la nube. Para monitoreo autohospedado:
# 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
Para monitorear la producción, exponga las métricas de PM2 a 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']
Integración del script de implementación
Una secuencia de implementación típica:
#!/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 ==="
Errores y soluciones comunes
Error 1: Olvidar --update-env
Después de actualizar .env.local, ejecutar pm2 restart all sin --update-env hace que los procesos se reinicien con las variables de entorno antiguas. Utilice siempre pm2 restart ecosystem.config.cjs --update-env.
Error 2: usar watch: true en producción
watch: true reinicia el proceso cuando cambia algún archivo. En producción, los resultados de la compilación cambian en cada implementación, lo que provoca infinitos bucles de reinicio. Establezca siempre watch: false.
Error 3: No manejar SIGTERM para un cierre ordenado
PM2 envía SIGTERM al reiniciar/detener. Si su aplicación no lo maneja, PM2 espera kill_timeout milisegundos y envía SIGKILL, lo que puede provocar la pérdida de solicitudes. Manejar SIGTERM en 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);
});
Error 4: quedarse sin espacio en el disco de registro de PM2
Sin pm2-logrotate, los registros de PM2 crecen indefinidamente. Una API con mucho tráfico puede generar gigabytes de registros por día. Instale pm2-logrotate inmediatamente y establezca un max_size (50 MB) y un retain (7 días) razonables.
Error 5: Perder procesos después de reiniciar
pm2 start no persiste los procesos entre reinicios. Ejecute siempre pm2 startup + pm2 save después de la configuración inicial. Si los procesos desaparecen después de reiniciar, ejecute pm2 resurrect para restaurar desde el volcado guardado.
Preguntas frecuentes
¿Cuándo debo utilizar el modo clúster frente al modo bifurcación?
Utilice el modo de clúster para cargas de trabajo vinculadas a la CPU (API de NestJS con computación pesada y procesamiento de datos). El modo de clúster genera instances procesos de trabajo y equilibrios de carga de PM2 entre ellos, aprovechando todos los núcleos de la CPU. Utilice el modo de bifurcación para cargas de trabajo vinculadas a E/S (Next.js, servicio de archivos estáticos) o cuando el proceso no admite la agrupación en clústeres (scripts de un solo subproceso, servicio Docusaurus). Next.js maneja sus propios subprocesos de trabajo internamente, por lo que el modo bifurcación con instances: 1 es correcto.
¿Cómo ejecuto PM2 en un contenedor Docker?
PM2 en Docker usa pm2-docker (o pm2-runtime) en lugar de pm2 para manejar las señales correctamente. La versión en tiempo de ejecución no demoniza (lo que provocaría que Docker se cerrara), reenvía correctamente señales a procesos secundarios y registra en stdout/stderr en lugar de archivos. Utilice CMD ["pm2-runtime", "ecosystem.config.cjs"] en su Dockerfile.
¿Cómo superviso los procesos de PM2 desde una máquina remota?
PM2 Plus (servicio en la nube de pago por proceso) proporciona un panel web. Para el monitoreo autohospedado, exponga las métricas de PM2 a través del exportador Prometheus y visualícelas en Grafana. Para verificaciones de estado simples, puede utilizar SSH y ejecutar pm2 status, o exponer las métricas a través de un punto final HTTP que sondea su sistema de monitoreo.
¿Cuál es la diferencia entre recargar pm2 y reiniciar pm2?
pm2 restart mata a todos los trabajadores simultáneamente y los reinicia; hay un breve período sin trabajadores en ejecución (tiempo de inactividad). pm2 reload es elegante: inicia nuevos trabajadores, espera a que estén listos y luego cierra a los trabajadores antiguos: cero tiempo de inactividad. Utilice pm2 reload para implementaciones de producción. Nota: la recarga solo funciona correctamente en modo clúster; El modo bifurcación retrocede para reiniciar el comportamiento.
¿Cómo configuro diferentes variables de entorno para diferentes procesos?
Cada proceso en ecosystem.config.cjs tiene sus propias secciones env y env_production. La sección env_production se utiliza cuando pasa --env production a comandos PM2. Para los secretos, nunca los coloque directamente en el archivo del ecosistema; configúrelos en el entorno del sistema o en el archivo .env.local y deje que PM2 los herede. El indicador --update-env garantiza que PM2 vuelva a leer las variables de entorno al reiniciar.
Próximos pasos
PM2 es una parte fundamental de cualquier implementación de producción de Node.js. ECOSIRE gestiona cinco procesos PM2 en producción (Next.js, NestJS, Docusaurus y dos sitios de marca) con reinicios automáticos, rotación de registros e implementaciones sin tiempo de inactividad en cada paso a principal.
Ya sea que necesite soporte de ingeniería de DevOps, arquitectura de implementación de producción o ayuda para migrar a una configuración en contenedores, explore nuestros servicios para ver cómo podemos ayudarlo.
Escrito por
ECOSIRE Research and Development Team
Construyendo productos digitales de nivel empresarial en ECOSIRE. Compartiendo perspectivas sobre integraciones Odoo, automatización de eCommerce y soluciones empresariales impulsadas por IA.
Artículos relacionados
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.