Set up comprehensive PM2 monitoring with automated log rotation, Prometheus metrics, cluster mode management, and production-ready health checks for Node.js applications.
Prerequisites
- Root or sudo access
- Node.js application to monitor
- Basic understanding of systemd services
What this solves
PM2 is the de facto process manager for Node.js production deployments, but proper monitoring and log management requires additional configuration. This tutorial sets up automated log rotation, integrates PM2 with Prometheus for metrics collection, configures cluster mode for high availability, and implements health checks with alerting.
Step-by-step installation
Install Node.js and PM2
Start by installing the latest LTS version of Node.js and PM2 globally on your system.
curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash -
sudo apt update
sudo apt install -y nodejs
sudo npm install -g pm2
pm2 install pm2-logrotate
pm2 install pm2-auto-pull
Create a sample Node.js application
Create a basic Express application to demonstrate PM2 monitoring and logging features.
mkdir -p /opt/nodeapp
cd /opt/nodeapp
{
"name": "sample-node-app",
"version": "1.0.0",
"description": "Sample app for PM2 monitoring",
"main": "app.js",
"dependencies": {
"express": "^4.18.0",
"prom-client": "^15.0.0",
"winston": "^3.8.0"
}
}
npm install
Create application with logging and metrics
Build a sample application that includes structured logging and Prometheus metrics endpoints.
const express = require('express');
const winston = require('winston');
const client = require('prom-client');
const app = express();
const port = process.env.PORT || 3000;
// Configure Winston logger
const logger = winston.createLogger({
level: 'info',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.json()
),
transports: [
new winston.transports.File({ filename: '/var/log/nodeapp/error.log', level: 'error' }),
new winston.transports.File({ filename: '/var/log/nodeapp/combined.log' }),
new winston.transports.Console()
]
});
// Prometheus metrics
const register = new client.Registry();
const httpRequestDuration = new client.Histogram({
name: 'http_request_duration_seconds',
help: 'Duration of HTTP requests in seconds',
labelNames: ['method', 'route', 'status_code']
});
const httpRequestTotal = new client.Counter({
name: 'http_requests_total',
help: 'Total number of HTTP requests',
labelNames: ['method', 'route', 'status_code']
});
register.registerMetric(httpRequestDuration);
register.registerMetric(httpRequestTotal);
client.collectDefaultMetrics({ register });
// Middleware for metrics
app.use((req, res, next) => {
const start = Date.now();
res.on('finish', () => {
const duration = (Date.now() - start) / 1000;
httpRequestDuration.observe(
{ method: req.method, route: req.route?.path || req.path, status_code: res.statusCode },
duration
);
httpRequestTotal.inc({
method: req.method,
route: req.route?.path || req.path,
status_code: res.statusCode
});
logger.info(${req.method} ${req.path} ${res.statusCode} ${duration}s);
});
next();
});
// Routes
app.get('/', (req, res) => {
res.json({ message: 'Hello from PM2 monitored app!', timestamp: new Date().toISOString() });
});
app.get('/health', (req, res) => {
res.json({ status: 'healthy', uptime: process.uptime(), memory: process.memoryUsage() });
});
app.get('/metrics', (req, res) => {
res.set('Content-Type', register.contentType);
res.end(register.metrics());
});
app.get('/error', (req, res) => {
logger.error('Intentional error for testing');
res.status(500).json({ error: 'This is a test error' });
});
app.listen(port, () => {
logger.info(Server running on port ${port});
});
Create log directory and set permissions
Create the log directory structure with appropriate permissions for the Node.js application.
sudo mkdir -p /var/log/nodeapp
sudo chown $USER:$USER /var/log/nodeapp
sudo chmod 755 /var/log/nodeapp
Configure PM2 ecosystem file
Create a comprehensive PM2 ecosystem configuration that enables cluster mode, monitoring, and proper logging.
module.exports = {
apps: [{
name: 'nodeapp-cluster',
script: './app.js',
instances: 'max',
exec_mode: 'cluster',
env: {
NODE_ENV: 'production',
PORT: 3000
},
log_file: '/var/log/nodeapp/app.log',
out_file: '/var/log/nodeapp/out.log',
error_file: '/var/log/nodeapp/error.log',
log_date_format: 'YYYY-MM-DD HH:mm:ss Z',
merge_logs: true,
max_memory_restart: '300M',
restart_delay: 4000,
max_restarts: 10,
min_uptime: '10s',
kill_timeout: 5000,
listen_timeout: 8000,
shutdown_with_message: true,
pmx: true,
monitoring: true,
ignore_watch: ['node_modules', 'logs'],
watch_options: {
followSymlinks: false
}
}]
};
Configure PM2 log rotation
Set up automated log rotation to prevent disk space issues and maintain manageable log file sizes.
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_HH-mm-ss'
pm2 set pm2-logrotate:rotateModule true
pm2 set pm2-logrotate:workerInterval 3600
pm2 set pm2-logrotate:rotateInterval '0 0 *'
Configure system logrotate integration
Create a system logrotate configuration for additional log file management and integration with systemd journal.
/var/log/nodeapp/*.log {
daily
missingok
rotate 14
compress
delaycompress
notifempty
sharedscripts
copytruncate
postrotate
systemctl reload rsyslog > /dev/null 2>&1 || true
endscript
}
sudo chown root:root /etc/logrotate.d/nodeapp
sudo chmod 644 /etc/logrotate.d/nodeapp
Install and configure Prometheus
Install Prometheus to collect metrics from your PM2 applications and system resources.
sudo useradd --no-create-home --shell /bin/false prometheus
wget https://github.com/prometheus/prometheus/releases/download/v2.45.0/prometheus-2.45.0.linux-amd64.tar.gz
tar xzf prometheus-2.45.0.linux-amd64.tar.gz
sudo cp prometheus-2.45.0.linux-amd64/prometheus /usr/local/bin/
sudo cp prometheus-2.45.0.linux-amd64/promtool /usr/local/bin/
sudo chown prometheus:prometheus /usr/local/bin/prometheus
sudo chown prometheus:prometheus /usr/local/bin/promtool
sudo mkdir -p /etc/prometheus /var/lib/prometheus
sudo chown prometheus:prometheus /etc/prometheus /var/lib/prometheus
Configure Prometheus for PM2 monitoring
Set up Prometheus configuration to scrape metrics from PM2 applications and Node Exporter.
global:
scrape_interval: 15s
evaluation_interval: 15s
rule_files:
- "/etc/prometheus/rules/*.yml"
alerting:
alertmanagers:
- static_configs:
- targets:
- localhost:9093
scrape_configs:
- job_name: 'prometheus'
static_configs:
- targets: ['localhost:9090']
- job_name: 'node-exporter'
static_configs:
- targets: ['localhost:9100']
scrape_interval: 5s
- job_name: 'nodeapp-pm2'
static_configs:
- targets: ['localhost:3000']
metrics_path: '/metrics'
scrape_interval: 10s
- job_name: 'pm2-metrics'
static_configs:
- targets: ['localhost:9209']
scrape_interval: 10s
sudo chown prometheus:prometheus /etc/prometheus/prometheus.yml
Install Node Exporter for system metrics
Install Prometheus Node Exporter to collect detailed system metrics alongside PM2 application metrics.
wget https://github.com/prometheus/node_exporter/releases/download/v1.6.1/node_exporter-1.6.1.linux-amd64.tar.gz
tar xzf node_exporter-1.6.1.linux-amd64.tar.gz
sudo cp node_exporter-1.6.1.linux-amd64/node_exporter /usr/local/bin/
sudo useradd --no-create-home --shell /bin/false node_exporter
sudo chown node_exporter:node_exporter /usr/local/bin/node_exporter
Install PM2 Prometheus exporter
Install a dedicated PM2 metrics exporter to expose process-level metrics to Prometheus.
sudo npm install -g pm2-prometheus-exporter
pm2 install pm2-prometheus-exporter
Create systemd services
Create systemd service files for Prometheus, Node Exporter, and PM2 to ensure automatic startup and proper service management.
[Unit]
Description=Prometheus
Wants=network-online.target
After=network-online.target
[Service]
User=prometheus
Group=prometheus
Type=simple
ExecStart=/usr/local/bin/prometheus \
--config.file /etc/prometheus/prometheus.yml \
--storage.tsdb.path /var/lib/prometheus/ \
--web.console.templates=/etc/prometheus/consoles \
--web.console.libraries=/etc/prometheus/console_libraries \
--web.listen-address=0.0.0.0:9090 \
--web.enable-lifecycle
Restart=always
[Install]
WantedBy=multi-user.target
[Unit]
Description=Node Exporter
Wants=network-online.target
After=network-online.target
[Service]
User=node_exporter
Group=node_exporter
Type=simple
ExecStart=/usr/local/bin/node_exporter --web.listen-address=:9100
Restart=always
[Install]
WantedBy=multi-user.target
[Unit]
Description=PM2 process manager for Node.js applications
Documentation=https://pm2.keymetrics.io/
After=network.target
[Service]
Type=forking
User=$USER
LimitNOFILE=infinity
LimitNPROC=infinity
LimitCORE=infinity
Environment=PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
Environment=PM2_HOME=/home/$USER/.pm2
PIDFile=/home/$USER/.pm2/pm2.pid
ExecStart=/usr/local/bin/pm2 resurrect
ExecReload=/usr/local/bin/pm2 reload all
ExecStop=/usr/local/bin/pm2 kill
PrivateTmp=false
[Install]
WantedBy=multi-user.target
Start PM2 application with monitoring
Deploy the application using PM2 ecosystem configuration and enable monitoring features.
cd /opt/nodeapp
pm2 start ecosystem.config.js
pm2 save
pm2 startup
sudo env PATH=$PATH:/usr/local/bin /usr/local/lib/node_modules/pm2/bin/pm2 startup systemd -u $USER --hp /home/$USER
Configure PM2 monitoring dashboard
Enable PM2's built-in monitoring dashboard and configure web interface access.
pm2 set pm2:web-interface true
pm2 set pm2:web-port 9615
pm2 set pm2:web-host 0.0.0.0
pm2 web
Create Prometheus alerting rules
Configure alerting rules for PM2 applications to monitor process health, memory usage, and error rates.
sudo mkdir -p /etc/prometheus/rules
groups:
- name: pm2-alerts
rules:
- alert: PM2ProcessDown
expr: pm2_process_status == 0
for: 30s
labels:
severity: critical
annotations:
summary: "PM2 process {{ $labels.name }} is down"
description: "PM2 process {{ $labels.name }} has been down for more than 30 seconds"
- alert: PM2HighMemoryUsage
expr: (pm2_process_memory_usage / pm2_process_memory_limit) * 100 > 80
for: 2m
labels:
severity: warning
annotations:
summary: "PM2 process {{ $labels.name }} high memory usage"
description: "PM2 process {{ $labels.name }} is using {{ $value }}% of allocated memory"
- alert: PM2HighRestarts
expr: increase(pm2_process_restarts_total[5m]) > 3
for: 1m
labels:
severity: warning
annotations:
summary: "PM2 process {{ $labels.name }} restarting frequently"
description: "PM2 process {{ $labels.name }} has restarted {{ $value }} times in the last 5 minutes"
- alert: NodeAppHighErrorRate
expr: rate(http_requests_total{status_code=~"5.."}[5m]) / rate(http_requests_total[5m]) > 0.1
for: 2m
labels:
severity: critical
annotations:
summary: "High error rate in Node.js application"
description: "Error rate is {{ $value | humanizePercentage }} over the last 5 minutes"
sudo chown -R prometheus:prometheus /etc/prometheus/rules
Configure health check monitoring
Set up automated health checks for PM2 applications with custom monitoring scripts.
const http = require('http');
const fs = require('fs');
const path = require('path');
function healthCheck() {
const options = {
hostname: 'localhost',
port: 3000,
path: '/health',
method: 'GET',
timeout: 5000
};
const req = http.request(options, (res) => {
let data = '';
res.on('data', chunk => data += chunk);
res.on('end', () => {
if (res.statusCode === 200) {
const response = JSON.parse(data);
console.log(Health check passed: ${JSON.stringify(response)});
process.exit(0);
} else {
console.error(Health check failed with status: ${res.statusCode});
process.exit(1);
}
});
});
req.on('error', (err) => {
console.error(Health check failed: ${err.message});
process.exit(1);
});
req.on('timeout', () => {
console.error('Health check timeout');
req.destroy();
process.exit(1);
});
req.end();
}
healthCheck();
chmod +x /opt/nodeapp/healthcheck.js
Enable systemd services and start monitoring
Enable all monitoring services and verify they start correctly.
sudo systemctl daemon-reload
sudo systemctl enable --now prometheus
sudo systemctl enable --now node-exporter
sudo systemctl enable --now pm2-nodeapp
sudo systemctl status prometheus node-exporter pm2-nodeapp
Configure systemd journal integration
Configure PM2 journal logging
Integrate PM2 logs with systemd journal for centralized log management and better integration with system monitoring.
# PM2 log forwarding to systemd journal
$ModLoad imfile
PM2 application logs
$InputFileName /var/log/nodeapp/combined.log
$InputFileTag pm2-nodeapp:
$InputFileStateFile stat-pm2-nodeapp
$InputFileSeverity info
$InputFileFacility local0
$InputRunFileMonitor
PM2 error logs
$InputFileName /var/log/nodeapp/error.log
$InputFileTag pm2-nodeapp-error:
$InputFileStateFile stat-pm2-nodeapp-error
$InputFileSeverity error
$InputFileFacility local0
$InputRunFileMonitor
Send to journal
local0.* :omjournal:
sudo systemctl restart rsyslog
Set up advanced monitoring dashboards
Install Grafana for visualization
Install Grafana to create comprehensive dashboards for PM2 and application monitoring. For detailed Grafana setup, see our Grafana installation guide.
wget -q -O - https://packages.grafana.com/gpg.key | sudo apt-key add -
echo "deb https://packages.grafana.com/oss/deb stable main" | sudo tee /etc/apt/sources.list.d/grafana.list
sudo apt update
sudo apt install -y grafana
sudo systemctl enable --now grafana-server
Configure automated alerting
Set up automated alerts for PM2 process failures and performance issues using a simple notification script.
#!/bin/bash
PM2 Alert Handler Script
ALERT_TYPE=$1
PROCESS_NAME=$2
MESSAGE=$3
LOG_FILE="/var/log/nodeapp/alerts.log"
case $ALERT_TYPE in
"restart")
echo "$(date): ALERT - Process $PROCESS_NAME restarted: $MESSAGE" | tee -a $LOG_FILE
# Add webhook or email notification here
;;
"memory")
echo "$(date): WARNING - Process $PROCESS_NAME high memory usage: $MESSAGE" | tee -a $LOG_FILE
;;
"error")
echo "$(date): ERROR - Process $PROCESS_NAME error: $MESSAGE" | tee -a $LOG_FILE
;;
esac
Optional: Send to external monitoring service
curl -X POST "$WEBHOOK_URL" -H "Content-Type: application/json" \
-d "{\"alert\": \"$ALERT_TYPE\", \"process\": \"$PROCESS_NAME\", \"message\": \"$MESSAGE\"}"
exit 0
chmod +x /opt/nodeapp/alert-handler.sh
Verify your setup
Test all components of your PM2 monitoring setup to ensure proper functionality.
# Check PM2 process status
pm2 list
pm2 monit
Verify log rotation is working
pm2 logs --lines 20
ls -la /var/log/nodeapp/
Test application endpoints
curl http://localhost:3000/
curl http://localhost:3000/health
curl http://localhost:3000/metrics
Check Prometheus targets
curl http://localhost:9090/api/v1/targets
Verify Node Exporter metrics
curl http://localhost:9100/metrics | head -20
Check systemd journal integration
journalctl -u pm2-nodeapp -f
Test health check script
node /opt/nodeapp/healthcheck.js
Verify Prometheus is collecting PM2 metrics
curl -G http://localhost:9090/api/v1/query --data-urlencode 'query=pm2_process_memory_usage'
Check Grafana dashboard access
curl -I http://localhost:3000
Common issues
| Symptom | Cause | Fix |
|---|---|---|
| PM2 processes won't start | Permission issues or invalid ecosystem config | Check pm2 logs and verify file permissions with ls -la /opt/nodeapp |
| Log rotation not working | PM2 logrotate module not configured | Run pm2 describe pm2-logrotate and reconfigure with pm2 set pm2-logrotate:max_size 50M |
| Prometheus not scraping PM2 metrics | Target endpoint unreachable or wrong port | Check curl http://localhost:3000/metrics and verify Prometheus config |
| Systemd service fails to start | User permissions or wrong paths in service file | Check systemctl status pm2-nodeapp and verify user has proper permissions |
| High memory usage alerts | Memory leak or insufficient resource limits | Monitor with pm2 monit and adjust max_memory_restart in ecosystem config |
| Grafana dashboard shows no data | Prometheus data source not configured correctly | Verify Prometheus is running on port 9090 and add data source in Grafana settings |
Next steps
- Set up NGINX reverse proxy with PM2 clustering for load balancing and SSL termination
- Implement comprehensive Node.js monitoring with custom metrics and advanced alerting
- Configure Prometheus Alertmanager for Slack and email notifications
- Set up centralized logging with ELK stack for advanced log analysis and searching
- Configure PM2 blue-green deployments for zero-downtime application updates
Running this in production?
Automated install script
Run this to automate the entire setup
#!/usr/bin/env bash
set -euo pipefail
# PM2 Production Setup with Monitoring and Log Rotation
# Supports Ubuntu/Debian and RHEL-based distributions
# Color definitions
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
# Configuration
APP_USER="${1:-nodeapp}"
APP_DIR="/opt/nodeapp"
LOG_DIR="/var/log/nodeapp"
NODE_ENV="${2:-production}"
usage() {
echo "Usage: $0 [app_user] [environment]"
echo " app_user: User to run the application (default: nodeapp)"
echo " environment: Node environment (default: production)"
exit 1
}
cleanup() {
echo -e "${RED}[ERROR]${NC} Installation failed. Cleaning up..."
sudo systemctl stop pm2-$APP_USER 2>/dev/null || true
sudo userdel -r $APP_USER 2>/dev/null || true
sudo rm -rf $APP_DIR $LOG_DIR
exit 1
}
trap cleanup ERR
echo -e "${GREEN}PM2 Production Setup Starting...${NC}"
# Check prerequisites
if [[ $EUID -ne 0 ]] && ! sudo -n true 2>/dev/null; then
echo -e "${RED}[ERROR]${NC} This script requires sudo privileges"
exit 1
fi
# Detect distribution
echo -e "${YELLOW}[1/12]${NC} Detecting distribution..."
if [ -f /etc/os-release ]; then
. /etc/os-release
case "$ID" in
ubuntu|debian)
PKG_MGR="apt"
PKG_INSTALL="apt install -y"
PKG_UPDATE="apt update"
FIREWALL_CMD="ufw"
;;
almalinux|rocky|centos|rhel|ol|fedora)
PKG_MGR="dnf"
PKG_INSTALL="dnf install -y"
PKG_UPDATE="dnf check-update"
FIREWALL_CMD="firewall-cmd"
;;
amzn)
PKG_MGR="yum"
PKG_INSTALL="yum install -y"
PKG_UPDATE="yum check-update"
FIREWALL_CMD="firewall-cmd"
;;
*)
echo -e "${RED}[ERROR]${NC} Unsupported distribution: $ID"
exit 1
;;
esac
else
echo -e "${RED}[ERROR]${NC} Cannot detect distribution"
exit 1
fi
echo -e "${GREEN}[SUCCESS]${NC} Detected $PRETTY_NAME"
# Update package manager
echo -e "${YELLOW}[2/12]${NC} Updating package manager..."
if [ "$PKG_MGR" = "apt" ]; then
sudo $PKG_UPDATE
else
sudo $PKG_UPDATE || true
fi
# Install prerequisites
echo -e "${YELLOW}[3/12]${NC} Installing prerequisites..."
sudo $PKG_INSTALL curl wget gnupg2
# Install Node.js
echo -e "${YELLOW}[4/12]${NC} Installing Node.js LTS..."
if [ "$PKG_MGR" = "apt" ]; then
curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash -
sudo $PKG_INSTALL nodejs
else
curl -fsSL https://rpm.nodesource.com/setup_lts.x | sudo bash -
sudo $PKG_INSTALL nodejs npm
fi
# Create application user
echo -e "${YELLOW}[5/12]${NC} Creating application user..."
if ! id "$APP_USER" &>/dev/null; then
sudo useradd -r -s /bin/bash -d /home/$APP_USER -m $APP_USER
fi
# Install PM2 globally
echo -e "${YELLOW}[6/12]${NC} Installing PM2..."
sudo npm install -g pm2
sudo -u $APP_USER pm2 install pm2-logrotate
sudo -u $APP_USER pm2 install pm2-auto-pull
# Create directories
echo -e "${YELLOW}[7/12]${NC} Setting up directories..."
sudo mkdir -p $APP_DIR $LOG_DIR
sudo chown $APP_USER:$APP_USER $APP_DIR $LOG_DIR
sudo chmod 755 $APP_DIR $LOG_DIR
# Create sample application
echo -e "${YELLOW}[8/12]${NC} Creating sample application..."
sudo -u $APP_USER bash << EOF
cd $APP_DIR
cat > package.json << 'EOL'
{
"name": "sample-node-app",
"version": "1.0.0",
"description": "Sample app for PM2 monitoring",
"main": "app.js",
"dependencies": {
"express": "^4.18.0",
"prom-client": "^15.0.0",
"winston": "^3.8.0"
}
}
EOL
npm install
cat > app.js << 'EOL'
const express = require('express');
const winston = require('winston');
const client = require('prom-client');
const app = express();
const port = process.env.PORT || 3000;
const logger = winston.createLogger({
level: 'info',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.json()
),
transports: [
new winston.transports.File({ filename: '$LOG_DIR/error.log', level: 'error' }),
new winston.transports.File({ filename: '$LOG_DIR/combined.log' }),
new winston.transports.Console()
]
});
const register = new client.Registry();
const httpRequestDuration = new client.Histogram({
name: 'http_request_duration_seconds',
help: 'Duration of HTTP requests in seconds',
labelNames: ['method', 'route', 'status_code']
});
const httpRequestTotal = new client.Counter({
name: 'http_requests_total',
help: 'Total number of HTTP requests',
labelNames: ['method', 'route', 'status_code']
});
register.registerMetric(httpRequestDuration);
register.registerMetric(httpRequestTotal);
client.collectDefaultMetrics({ register });
app.use((req, res, next) => {
const start = Date.now();
res.on('finish', () => {
const duration = (Date.now() - start) / 1000;
httpRequestDuration.observe(
{ method: req.method, route: req.route?.path || req.path, status_code: res.statusCode },
duration
);
httpRequestTotal.inc({
method: req.method,
route: req.route?.path || req.path,
status_code: res.statusCode
});
logger.info(\`\${req.method} \${req.path} \${res.statusCode} \${duration}s\`);
});
next();
});
app.get('/', (req, res) => {
res.json({ message: 'Hello from PM2 monitored app!', timestamp: new Date().toISOString() });
});
app.get('/health', (req, res) => {
res.json({ status: 'healthy', uptime: process.uptime(), memory: process.memoryUsage() });
});
app.get('/metrics', (req, res) => {
res.set('Content-Type', register.contentType);
res.end(register.metrics());
});
app.listen(port, () => {
logger.info(\`Server running on port \${port}\`);
});
EOL
EOF
# Configure PM2
echo -e "${YELLOW}[9/12]${NC} Configuring PM2..."
sudo -u $APP_USER bash << EOF
cd $APP_DIR
cat > ecosystem.config.js << 'EOL'
module.exports = {
apps: [{
name: 'sample-app',
script: './app.js',
instances: 'max',
exec_mode: 'cluster',
env: {
NODE_ENV: '$NODE_ENV',
PORT: 3000
},
log_file: '$LOG_DIR/pm2.log',
out_file: '$LOG_DIR/pm2-out.log',
error_file: '$LOG_DIR/pm2-error.log',
max_memory_restart: '1G',
kill_timeout: 3000
}]
};
EOL
pm2 set pm2-logrotate:max_size 10M
pm2 set pm2-logrotate:retain 30
pm2 set pm2-logrotate:compress true
pm2 start ecosystem.config.js
pm2 save
EOF
# Setup PM2 startup
echo -e "${YELLOW}[10/12]${NC} Setting up PM2 startup..."
STARTUP_CMD=$(sudo -u $APP_USER pm2 startup | grep "sudo env" || true)
if [ -n "$STARTUP_CMD" ]; then
eval "$STARTUP_CMD"
fi
# Configure firewall
echo -e "${YELLOW}[11/12]${NC} Configuring firewall..."
if command -v ufw &> /dev/null; then
sudo ufw allow 3000/tcp
elif command -v firewall-cmd &> /dev/null; then
sudo firewall-cmd --permanent --add-port=3000/tcp
sudo firewall-cmd --reload
fi
# Verification
echo -e "${YELLOW}[12/12]${NC} Verifying installation..."
sleep 5
if sudo -u $APP_USER pm2 status | grep -q "online"; then
echo -e "${GREEN}[SUCCESS]${NC} PM2 is running"
else
echo -e "${RED}[ERROR]${NC} PM2 is not running properly"
exit 1
fi
if curl -s http://localhost:3000/health | grep -q "healthy"; then
echo -e "${GREEN}[SUCCESS]${NC} Application is responding"
else
echo -e "${RED}[ERROR]${NC} Application is not responding"
exit 1
fi
echo -e "${GREEN}[COMPLETE]${NC} PM2 setup finished successfully!"
echo -e "${GREEN}Application URL:${NC} http://localhost:3000"
echo -e "${GREEN}Health Check:${NC} http://localhost:3000/health"
echo -e "${GREEN}Metrics:${NC} http://localhost:3000/metrics"
echo -e "${GREEN}Logs:${NC} $LOG_DIR/"
echo -e "${GREEN}PM2 Commands:${NC}"
echo " sudo -u $APP_USER pm2 status"
echo " sudo -u $APP_USER pm2 logs"
echo " sudo -u $APP_USER pm2 monit"
Review the script before running. Execute with: bash install.sh