Que PM2 sobreviva a un reinicio del servidor

El servidor reinició y la web no volvió. Volvió una hora después, cuando alguien lanzó un despliegue. Parece un problema de despliegue y no lo es: lo único que hizo ese despliegue fue ejecutar pm2 start otra vez a mano.

Para que tu aplicación arranque sola hacen falta dos cosas distintas, y cada una sin la otra no sirve de nada.

Las dos mitades

pm2 save escribe la lista de procesos actual en ~/.pm2/dump.pm2. PM2 relanza lo que haya en ese fichero y nada más. Un script de despliegue que hace pm2 delete y pm2 start pero no guarda deja el dump desfasado, o vacío si nunca llegó a escribirse.

pm2 startup instala una unidad de systemd que ejecuta pm2 resurrect en el arranque, y esa unidad es la que lee el dump.

Una sin la otra te da un arranque que no levanta nada, o una lista guardada que no lee nadie.

La trampa

Ejecutar pm2 startup con un usuario sin privilegios no instala absolutamente nada. Detecta systemd, calcula el comando que instalaría la unidad, lo imprime y termina. La salida es larga, va en verde y acaba con una línea sobre copiar y pegar el comando. Parece que ha funcionado. No ha pasado nada.

Así que ejecútalo como root, y comprueba el resultado con systemd, no con lo que te haya imprimido PM2.

El orden correcto

La aplicación tiene que estar levantada antes de guardar, o guardarás una lista vacía.

# como <user> — confirma que la app está corriendo
pm2 status
# como root — instala la unidad para ese usuario
env PATH=$PATH:/usr/bin pm2 startup systemd -u <user> --hp /home/<user>

El prefijo env PATH=$PATH:/usr/bin no es adorno. El PATH de root normalmente no lleva Node, y la unidad se queda con la ruta que tenía cuando se generó. Sin ese prefijo la unidad existe, pero en el arranque no encuentra node.

# otra vez como <user> — escribe el dump
pm2 save

Comprueba cada mitad por separado

Fallan por separado, así que se miran por separado.

systemctl is-enabled pm2-<user>

Tiene que responder enabled. Cualquier otra cosa, sea disabled o Failed to get unit file state, significa que la unidad nunca se instaló, diga lo que diga PM2.

grep -o '"name":"[^"]*"' /home/<user>/.pm2/dump.pm2

Ahí tiene que salir <app>. Si el fichero no existe, o el grep no imprime nada, es que pm2 save nunca se ejecutó con la aplicación levantada.

Pruébalo sin reiniciar

No hace falta reiniciar una máquina en producción para saber si esto funciona.

# como <user>
pm2 kill
# como root
systemctl start pm2-<user>
# como <user>
pm2 status
curl -sf http://127.0.0.1:3000/health

Si <app> vuelve a aparecer en línea, las dos mitades funcionan. Es exactamente lo que hace el arranque.

Nunca ejecutes pm2 save después de pm2 kill. Con el demonio muerto la lista de procesos está vacía, y al guardar escribes ese vacío en el dump, cargándote justo lo que estabas probando. Si lo haces por inercia, levanta la aplicación otra vez y repite el pm2 save.

Mete pm2 save en el script de despliegue

El dump se desfasa en cuanto un despliegue toca el proceso. Si tu script borra y vuelve a arrancar, tiene que guardar después:

pm2 delete <app> || true
pm2 start ecosystem.config.cjs --only <app>
pm2 save

El || true importa en la primera ejecución, cuando no hay nada que borrar y pm2 delete sale con código distinto de cero, lo que mata el script entero si tienes set -e.

Sin esa última línea todo lo anterior sigue funcionando hasta el primer despliegue que renombre la aplicación, cambie el número de instancias o toque el punto de entrada. A partir de ahí el dump describe un proceso que ya no existe, y el siguiente reinicio resucita eso.

Una cosa más sobre el orden de arranque

La unidad que se genera lleva After=network.target. No está ordenada después de tu base de datos. En un arranque en frío la aplicación puede levantarse antes de que MariaDB o PostgreSQL acepten conexiones, fallar en su primera consulta y salir.

Normalmente esto se arregla solo: PM2 reinicia el proceso, en el segundo o tercer intento la base de datos ya está lista y la web sube. La pista es un contador de reinicios por encima de cero en un proceso que por lo demás está sano, en la columna de pm2 status.

Si no se recupera, entonces sí, ordena la unidad de forma explícita:

sudo systemctl edit pm2-<user>
[Unit]
After=network.target mariadb.service

Añade eso solo si lo has visto fallar. Ordenar una unidad detrás de un servicio lento, o que vive en otra máquina, te cambia un arranque que reintenta por uno que se queda colgado.

Analiza tu web · Más artículos · Sobre AgentReady