pm2 Process Manager: Keep Your Node.js Bots Running Forever

DevOps #pm2#nodejs#devops#linux#automation
pm2 Process Manager: Keep Your Node.js Bots Running Forever

What You’ll Need

  • n8n Cloud or self-hosted n8n (for orchestrating automated workflows)
  • Hetzner VPS or Contabo VPS for hosting your Node.js processes
  • DigitalOcean as an alternative hosting provider
  • Node.js 18+ installed locally and on your server
  • SSH access to your VPS
  • PM2 (we’ll install this together)
  • A text editor (VS Code recommended)

Table of Contents

  1. Why PM2 Matters for Bot Automation
  2. Installation & Initial Setup
  3. Running Your First Process
  4. Clustering & Load Balancing
  5. Monitoring & Logs in Real-Time
  6. Auto-Restart on Server Reboot
  7. Integration with n8n Webhooks
  8. Getting Started

Why PM2 Matters for Bot Automation

I learned this the hard way. Three years ago, I deployed a Node.js bot to a VPS—no process manager. It crashed at 2 AM on a Sunday, sat dead for six hours, and tanked the entire automation pipeline. That was the day I discovered PM2.

If you’re running Node.js applications in production—whether it’s a webhook listener for n8n automation, a Telegram bot, a Discord integration, or a custom API server—you need process management. PM2 is the industry standard because it:

  • Restarts crashed processes automatically (no manual intervention)
  • Clusters your app across CPU cores (load balancing built-in)
  • Manages logs (no more digging through syslog)
  • Survives server reboots (auto-start on startup)
  • Monitors memory and CPU (alerts when things go wrong)
  • Handles zero-downtime deployments (reload without dropping connections)

When you’re building automation workflows that depend on external services, uptime isn’t optional. Unlike managed platforms, a VPS doesn’t come with built-in process monitoring. PM2 fills that gap for under $5/month in hosting costs (plus zero subscription fees).


Installation & Initial Setup

SSH into your server and install PM2 globally:

sudo npm install -g pm2

Verify the installation:

pm2 --version

You should see version 5.x or higher. Next, create a directory for your bot project:

mkdir -p ~/projects/automation-bot
cd ~/projects/automation-bot
npm init -y

Install Express and Axios as dependencies (we’ll use these for webhook handling):

npm install express axios dotenv

Now create your first .env file to store configuration:

cat > .env << 'EOF'
PORT=3001
NODE_ENV=production
N8N_WEBHOOK_URL=https://your-n8n-instance.com/webhook/automation
LOG_LEVEL=info
EOF

Running Your First Process

Let me show you a real example. This is a simple Express server that listens for webhooks and triggers actions. Create app.js:

const express = require('express');
const axios = require('axios');
require('dotenv').config();

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

app.use(express.json());

// Health check endpoint
app.get('/health', (req, res) => {
  res.status(200).json({ status: 'ok', timestamp: new Date().toISOString() });
});

// Webhook endpoint
app.post('/webhook/event', async (req, res) => {
  try {
    const { event_type, data } = req.body;
    
    console.log(`[${new Date().toISOString()}] Received event: ${event_type}`);
    
    // Send to n8n webhook
    const response = await axios.post(process.env.N8N_WEBHOOK_URL, {
      event_type,
      data,
      received_at: new Date().toISOString(),
      server_hostname: require('os').hostname()
    });
    
    res.status(200).json({ 
      success: true, 
      message: 'Event processed',
      n8n_response: response.status 
    });
  } catch (error) {
    console.error(`[ERROR] ${error.message}`);
    res.status(500).json({ 
      success: false, 
      error: error.message 
    });
  }
});

// Error handling middleware
app.use((err, req, res, next) => {
  console.error('Unhandled error:', err);
  res.status(500).json({ error: 'Internal server error' });
});

app.listen(PORT, () => {
  console.log(`[${new Date().toISOString()}] Server running on port ${PORT}`);
  console.log(`Health check: http://localhost:${PORT}/health`);
});

// Graceful shutdown
process.on('SIGTERM', () => {
  console.log('SIGTERM received, shutting down gracefully');
  process.exit(0);
});

Now start this with PM2:

pm2 start app.js --name "webhook-bot"

Check the status:

pm2 status

You’ll see output like:

┌─────┬──────────────┬─────────────┬──────┬────────┬──────────┐
│ id  │ name         │ namespace   │ mode │ status │ restart  │
├─────┼──────────────┼─────────────┼──────┼────────┼──────────┤
│ 0   │ webhook-bot  │ default     │ fork │ online │ 0        │
└─────┴──────────────┴─────────────┴──────┴────────┴──────────┘

Test it:

curl -X POST http://localhost:3001/webhook/event \
  -H "Content-Type: application/json" \
  -d '{"event_type":"test","data":{"message":"hello"}}'

💡 Fast-Track Your Project: Don’t want to configure this yourself? I build custom n8n pipelines and bots. Message me with code SYS3-HUGO.


Clustering & Load Balancing

Here’s where PM2 gets powerful. Instead of running a single process, you can spawn one worker per CPU core. This means if your server has 4 cores, PM2 will automatically load-balance across 4 instances of your app.

Create an ecosystem.config.js file in your project root:

module.exports = {
  apps: [
    {
      name: 'webhook-bot',
      script: './app.js',
      instances: 'max',
      exec_mode: 'cluster',
      env: {
        NODE_ENV: 'production',
        PORT: 3001
      },
      merge_logs: true,
      autorestart: true,
      watch: false,
      max_memory_restart: '500M',
      error_file: './logs/err.log',
      out_file: './logs/out.log',
      log_date_format: 'YYYY-MM-DD HH:mm:ss Z'
    }
  ]
};

Stop the current process and restart using this config:

pm2 stop webhook-bot
pm2 start ecosystem.config.js

Check the cluster:

pm2 status

Now you’ll see multiple instances (one per core):

┌─────┬──────────────┬─────────────┬──────────┬────────┬──────────┐
│ id  │ name         │ namespace   │ mode     │ status │ restart  │
├─────┼──────────────┼─────────────┼──────────┼────────┼──────────┤
│ 0   │ webhook-bot  │ default     │ cluster  │ online │ 0        │
│ 1   │ webhook-bot  │ default     │ cluster  │ online │ 0        │
│ 2   │ webhook-bot  │ default     │ cluster  │ online │ 0        │
│ 3   │ webhook-bot  │ default     │ cluster  │ online │ 0        │
└─────┴──────────────┴─────────────┴──────────┴──────────┴──────────┘

This is key for production. If one instance crashes, PM2 auto-restarts it while the others keep handling requests. When you’re comparing automation platforms—whether it’s n8n vs Make vs Zapier —self-hosted solutions like n8n running on your own VPS require this kind of reliability layer.


Monitoring & Logs in Real-Time

Watch all your processes in a dashboard:

pm2 monit

This shows CPU, memory, requests per minute, and restart counts. Press q to exit.

View logs for a specific app:

pm2 logs webhook-bot

Or tail the last 100 lines:

pm2 logs webhook-bot --lines 100

For a broader view of what’s happening, save all logs to files. The ecosystem.config.js I provided earlier already does this. Check your logs:

tail -f ~/projects/automation-bot/logs/out.log

You can also rotate logs automatically. Install the PM2 logrotate module:

pm2 install pm2-logrotate

Configure it to keep logs under 10MB:

pm2 set pm2-logrotate:max_size 10M
pm2 set pm2-logrotate:retain 7

This keeps your disk clean—critical when you’re running high-volume webhook receivers on a budget VPS like Hetzner or Contabo .


Auto-Restart on Server Reboot

If your VPS restarts (whether planned or due to a crash), PM2 needs to resurrect your processes. We do this by creating a startup script:

pm2 startup

This command outputs a line you need to run. It looks like:

sudo env PATH=$PATH:/usr/bin /usr/lib/node_modules/pm2/bin/pm2 startup systemd -u ubuntu --hp /home/ubuntu

Copy and run that exact line. Then save your current process list:

pm2 save

Now test it. Reboot your server:

sudo reboot

Wait 30 seconds, SSH back in, and check:

pm2 status

Your processes should be running. If they’re not, check the systemd service:

systemctl status pm2-ubuntu

Integration with n8n Webhooks

This is where it gets practical. When you’re running a self-hosted n8n instance, you often need a separate Node.js service to handle incoming webhooks and trigger workflows. PM2 keeps that service alive.

Here’s a real workflow: your PM2-managed bot listens on port 3001, receives events, and hits your n8n webhook to trigger an automation.

Modify your .env:

cat > .env << 'EOF'
PORT=3001
NODE_ENV=production
N8N_WEBHOOK_URL=https://your-n8n-domain.com/webhook/

Want to automate this yourself?

Start with n8n Cloud (free tier available) or self-host on a Hetzner VPS for full control.

📬 Get Weekly Automation Tips

One email per week with tutorials, tools, and workflows. No spam, unsubscribe anytime.

Subscribe Free →