--- title: "Cómo desplegar una app Node.js con PM2 en Ubuntu 24.04" description: "Despliega una app Node.js en Ubuntu 24.04 con PM2 en modo cluster, arranque automático con systemd, rotación de logs y Nginx como proxy inverso con HTTPS." category: "Despliegue de Aplicaciones" language: es source: https://cubepath.com/es/docs/despliegue-de-aplicaciones/despliegue-de-aplicacion-node-js-con-pm2 updated: 2026-09-25 --- # Cómo desplegar una app Node.js con PM2 en Ubuntu 24.04 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`: ```bash sudo npm install -g pm2 ``` Comprueba la versión: ```bash pm2 --version ``` ```output 6.x.x ``` > Nota: Si instalaste Node.js con nvm, ejecuta `npm install -g pm2` sin `sudo`. Ten en cuenta que en ese caso PM2 y Node.js solo existen para tu usuario. ## 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: ```bash mkdir -p ~/apps/myapp && cd ~/apps/myapp npm init -y npm install express ``` Crea el archivo principal: ```bash nano app.js ``` ```javascript 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: ```bash node app.js ``` ```output 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: ```bash nano ecosystem.config.js ``` ```javascript 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`. > Importante: No pongas secretos (contraseñas, claves de API) en este archivo si está en git. Guárdalos en un archivo `.env` fuera del repositorio y cárgalos desde la aplicación, o defínelos en el entorno del servidor. Arranca la aplicación en modo producción: ```bash pm2 start ecosystem.config.js --env production ``` Comprueba su estado: ```bash pm2 status ``` ```output 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: ```bash for i in 1 2 3 4; do curl -s http://127.0.0.1:3000/; done ``` ```output 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: ```bash pm2 startup systemd ``` PM2 no puede crear la unidad sin privilegios, así que imprime el comando exacto que debes ejecutar: ```output [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: ```bash pm2 save ``` ```output [PM2] Saving current process list... [PM2] Successfully saved in /home/your_user/.pm2/dump.pm2 ``` Comprueba que el servicio está habilitado: ```bash systemctl status pm2-your_user ``` ```output ● 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`. > Nota: Ejecuta `pm2 save` cada vez que añadas o elimines aplicaciones. Si no, tras el siguiente reinicio se restaurará la lista antigua. ## 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: ```bash pm2 install pm2-logrotate ``` Configura un tamaño máximo por archivo, cuántos archivos rotados conservar y que se compriman: ```bash 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: ```bash pm2 logs myapp --lines 50 ``` ```output [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: ```bash sudo nano /etc/nginx/sites-available/your_domain ``` ```nginx 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: ```bash 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: ```bash sudo apt install certbot python3-certbot-nginx sudo certbot --nginx -d your_domain ``` Comprueba el acceso desde fuera del servidor: ```bash curl https://your_domain/health ``` ```output {"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: ```bash 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: ```bash pm2 status ``` Diferencia entre los comandos de PM2 que reinician procesos: | Comando | Efecto | |---------|--------| | `pm2 reload myapp` | Reinicio escalonado sin cortes (modo cluster) | | `pm2 restart myapp` | Detiene y arranca todas las instancias a la vez; hay un corte breve | | `pm2 stop myapp` | Detiene la aplicación pero la mantiene en la lista | | `pm2 delete myapp` | La 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: ```bash 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: ```bash 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: ```bash 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.