PM2 es un gestor de procesos para Node.js que mantiene tus aplicaciones en marcha, las reinicia si fallan, reparte la carga entre todos los núcleos de la CPU en modo cluster y permite recargar el código sin cortar el servicio. En este tutorial desplegarás una aplicación Express en Ubuntu 24.04 con PM2, harás que arranque con el sistema, configurarás la rotación de logs y la publicarás detrás de Nginx con HTTPS.

Requisitos previos

Para seguir esta guía necesitas:

  • Un servidor con Ubuntu 24.04 LTS, por ejemplo un VPS de CubePath.
  • Un usuario no root con privilegios sudo. En los ejemplos se llama your_user.
  • Node.js 24 LTS y npm instalados desde el repositorio de NodeSource (node -v debe mostrar v24).
  • Nginx instalado (sudo apt install nginx).
  • Un dominio con un registro DNS A apuntando al servidor. En los ejemplos se usa your_domain.

Paso 1: Instalar PM2

Instala PM2 de forma global con npm. Con Node.js de NodeSource el directorio global pertenece a root, así que hace falta sudo:

sudo npm install -g pm2

Comprueba la versión:

pm2 --version
6.x.x

Paso 2: Crear la aplicación de ejemplo

Si ya tienes tu aplicación en el servidor (por ejemplo, clonada con git), salta al paso 3. Si no, crea una aplicación Express mínima. Colócala en tu directorio personal para que tu usuario sea el propietario y PM2 se ejecute sin privilegios:

mkdir -p ~/apps/myapp && cd ~/apps/myapp
npm init -y
npm install express

Crea el archivo principal:

nano app.js
const express = require('express');

const app = express();
const port = process.env.PORT || 3000;

app.get('/', (req, res) => {
  res.send(`Hola desde el proceso ${process.pid}\n`);
});

app.get('/health', (req, res) => {
  res.json({ status: 'ok', uptime: process.uptime() });
});

const server = app.listen(port, '127.0.0.1', () => {
  console.log(`Escuchando en 127.0.0.1:${port}`);
});

process.on('SIGINT', () => {
  server.close(() => process.exit(0));
});

La aplicación escucha solo en 127.0.0.1 porque Nginx será quien reciba el tráfico externo. El manejador de SIGINT cierra las conexiones abiertas antes de salir: PM2 envía esa señal al detener o recargar un proceso, así que las peticiones en curso terminan correctamente.

Pruébala directamente con Node.js:

node app.js
Escuchando en 127.0.0.1:3000

Detenla con Ctrl+C.

Paso 3: Definir la aplicación en un archivo ecosystem

Puedes arrancar una aplicación con pm2 start app.js, pero un archivo ecosystem guarda toda la configuración (nombre, instancias, variables de entorno, límites) en el repositorio del proyecto y hace que cada despliegue sea reproducible.

Crea el archivo en la raíz del proyecto:

nano ecosystem.config.js
module.exports = {
  apps: [
    {
      name: 'myapp',
      script: './app.js',
      cwd: __dirname,
      exec_mode: 'cluster',
      instances: 'max',
      max_memory_restart: '300M',
      kill_timeout: 5000,
      env: {
        NODE_ENV: 'development',
        PORT: 3000,
      },
      env_production: {
        NODE_ENV: 'production',
        PORT: 3000,
      },
    },
  ],
};

Qué hace cada opción:

  • exec_mode: 'cluster' e instances: 'max': arranca un proceso por núcleo de CPU. Todos comparten el puerto 3000 y PM2 reparte las conexiones entre ellos. Usa un número fijo (por ejemplo 2) si el servidor ejecuta otros servicios.
  • max_memory_restart: reinicia un proceso si supera 300 MB, una red de seguridad frente a fugas de memoria.
  • kill_timeout: milisegundos que PM2 espera a que el proceso termine tras SIGINT antes de forzarlo.
  • env_production: variables que se aplican al arrancar con --env production.

Arranca la aplicación en modo producción:

pm2 start ecosystem.config.js --env production

Comprueba su estado:

pm2 status
id  name   mode     pid    uptime  ↺  status  cpu  mem
0   myapp  cluster  41210  5s      0  online  0%   52.1mb
1   myapp  cluster  41217  5s      0  online  0%   51.8mb

Todas las instancias deben estar en online. Haz varias peticiones para ver cómo las atienden procesos distintos:

for i in 1 2 3 4; do curl -s http://127.0.0.1:3000/; done
Hola desde el proceso 41210
Hola desde el proceso 41217
Hola desde el proceso 41210
Hola desde el proceso 41217

Paso 4: Arrancar PM2 con el sistema

PM2 guarda la lista de procesos en memoria. Para que las aplicaciones vuelvan a arrancar tras un reinicio del servidor, genera una unidad de systemd para tu usuario:

pm2 startup systemd

PM2 no puede crear la unidad sin privilegios, así que imprime el comando exacto que debes ejecutar:

[PM2] To setup the Startup Script, copy/paste the following command:
sudo env PATH=$PATH:/usr/bin /usr/lib/node_modules/pm2/bin/pm2 startup systemd -u your_user --hp /home/your_user

Copia y ejecuta esa línea tal como aparece en tu terminal. Crea y habilita el servicio pm2-your_user.

Guarda la lista de procesos actual. Es la que PM2 restaurará al arrancar:

pm2 save
[PM2] Saving current process list...
[PM2] Successfully saved in /home/your_user/.pm2/dump.pm2

Comprueba que el servicio está habilitado:

systemctl status pm2-your_user
● pm2-your_user.service - PM2 process manager
     Loaded: loaded (/etc/systemd/system/pm2-your_user.service; enabled; preset: enabled)
     Active: active (running) since ...

Para confirmar que todo funciona, reinicia el servidor con sudo reboot, vuelve a conectarte y ejecuta pm2 status: las instancias de myapp deben aparecer en online.

Paso 5: Configurar la rotación de logs

PM2 guarda la salida de cada aplicación en ~/.pm2/logs/ y, por defecto, esos archivos crecen sin límite. Instala el módulo oficial de rotación:

pm2 install pm2-logrotate

Configura un tamaño máximo por archivo, cuántos archivos rotados conservar y que se compriman:

pm2 set pm2-logrotate:max_size 20M
pm2 set pm2-logrotate:retain 14
pm2 set pm2-logrotate:compress true

Consulta los logs de la aplicación cuando lo necesites:

pm2 logs myapp --lines 50
[TAILING] Tailing last 50 lines for [myapp] process (change the value with --lines option)
/home/your_user/.pm2/logs/myapp-out.log last 50 lines:
0|myapp    | Escuchando en 127.0.0.1:3000
1|myapp    | Escuchando en 127.0.0.1:3000

Pulsa Ctrl+C para salir. Para ver CPU y memoria de cada proceso en tiempo real, usa pm2 monit.

Paso 6: Publicar la aplicación con Nginx y HTTPS

Nginx recibirá el tráfico en los puertos 80 y 443, terminará TLS y reenviará las peticiones a la aplicación en 127.0.0.1:3000. Crea el bloque de servidor:

sudo nano /etc/nginx/sites-available/your_domain
server {
    listen 80;
    listen [::]:80;
    server_name your_domain;

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }
}

Las cabeceras Upgrade y Connection permiten WebSockets; las X-Forwarded-* le dicen a la aplicación la IP real del cliente y si la petición llegó por HTTPS. En Express, activa app.set('trust proxy', 'loopback') para que req.ip y req.protocol las usen.

Activa el sitio, abre el cortafuegos y recarga Nginx:

sudo ln -s /etc/nginx/sites-available/your_domain /etc/nginx/sites-enabled/
sudo nginx -t
sudo ufw allow 'Nginx Full'
sudo systemctl reload nginx

Obtén el certificado TLS con Certbot, que modificará el bloque para servir HTTPS y redirigir HTTP:

sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d your_domain

Comprueba el acceso desde fuera del servidor:

curl https://your_domain/health
{"status":"ok","uptime":312.48}

El puerto 3000 no debe estar abierto en UFW: la aplicación solo es accesible a través de Nginx.

Paso 7: Desplegar nuevas versiones sin cortes

En modo cluster, pm2 reload reinicia las instancias de una en una y espera a que cada nueva esté lista antes de detener la siguiente, así que el servicio no deja de responder. El flujo habitual de un despliegue desde git es:

cd ~/apps/myapp
git pull
npm ci --omit=dev
pm2 reload ecosystem.config.js --env production

Comprueba que las instancias siguen en online y que el contador de reinicios (↺) solo ha subido una vez por instancia:

pm2 status

Diferencia entre los comandos de PM2 que reinician procesos:

ComandoEfecto
pm2 reload myappReinicio escalonado sin cortes (modo cluster)
pm2 restart myappDetiene y arranca todas las instancias a la vez; hay un corte breve
pm2 stop myappDetiene la aplicación pero la mantiene en la lista
pm2 delete myappLa elimina de la lista de PM2

Si cambias variables de entorno en ecosystem.config.js, usa pm2 reload ecosystem.config.js --env production --update-env para que se apliquen, y después pm2 save.

Paso 8: Actualizar PM2

Tras actualizar el paquete global de PM2, el proceso que ya está en memoria sigue siendo el antiguo. pm2 update lo sustituye por el nuevo y restaura las aplicaciones:

sudo npm install -g pm2@latest
pm2 update

Después, vuelve a ejecutar pm2 startup systemd y el comando sudo que imprima, por si ha cambiado la ruta del binario.

Solución de problemas

La aplicación aparece como errored o el contador de reinicios no para de subir. La aplicación falla al arrancar. Consulta los errores:

pm2 logs myapp --err --lines 100

Un EADDRINUSE indica que otro proceso ocupa el puerto. Averigua cuál con sudo ss -ltnp 'sport = :3000'.

Nginx devuelve 502 Bad Gateway. La aplicación no está escuchando en la dirección de proxy_pass. Comprueba pm2 status y que curl http://127.0.0.1:3000/health responde desde el servidor. El log /var/log/nginx/error.log mostrará connect() failed (111: Connection refused).

Las aplicaciones no arrancan tras reiniciar el servidor. Falta pm2 save o la unidad de systemd no está habilitada. Revisa el servicio:

systemctl status pm2-your_user
sudo journalctl -u pm2-your_user -n 50 --no-pager

Si el servicio no existe, repite el paso 4.

Los procesos se reinician solos cada cierto tiempo. Probablemente superan max_memory_restart. pm2 show myapp indica el número de reinicios y pm2 monit la memoria en tiempo real. Sube el límite si el consumo es estable o busca la fuga si crece sin parar.

Conclusión

Tu aplicación Node.js se ejecuta ahora con PM2 en modo cluster en Ubuntu 24.04, arranca con el sistema mediante systemd, rota sus logs y se publica por HTTPS a través de Nginx. Las nuevas versiones se despliegan con git pull, npm ci y pm2 reload sin cortar el servicio.

Como siguientes pasos, automatiza ese despliegue desde tu sistema de CI, añade una comprobación externa del endpoint /health en tu herramienta de monitorización o configura límites de peticiones en Nginx para proteger la aplicación.