Skip to content

Staging Environment Setup with PM2

Complete guide for deploying the MMR SaaS Platform to a staging server using PM2 process manager (without Docker).

Table of Contents


Overview

This guide covers deploying to a staging environment using: - PM2: Process manager for Node.js applications - NGINX: Reverse proxy and web server - MySQL: Database server - Redis: Caching and session storage - Let's Encrypt: Free SSL certificates

Target Environment: Ubuntu 22.04 LTS (also works on 20.04, Debian 11+)


Server Prerequisites

Minimum Specifications

Resource Minimum Recommended
CPU 2 vCPU 4 vCPU
RAM 4 GB 8 GB
Storage 40 GB SSD 80 GB SSD
Bandwidth 100 Mbps 1 Gbps

Required Access

  • Root or sudo access
  • SSH key authentication (recommended)
  • Firewall control (UFW or similar)
  • Domain name with DNS control

Supported OS

  • Ubuntu 22.04 LTS (recommended)
  • Ubuntu 20.04 LTS
  • Debian 11+

Installing Dependencies

1. Update System Packages

# Connect to server
ssh user@your-server-ip

# Update package lists
sudo apt update

# Upgrade installed packages
sudo apt upgrade -y

# Install essential tools
sudo apt install -y curl wget git build-essential

2. Install Node.js 18.x

# Add NodeSource repository
curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash -

# Install Node.js
sudo apt install -y nodejs

# Verify installation
node --version  # Should show v18.x.x
npm --version   # Should show 9.x.x or higher

3. Install PNPM

# Install PNPM globally
npm install -g pnpm

# Verify installation
pnpm --version  # Should show 8.x.x or higher

4. Install PM2

# Install PM2 globally
npm install -g pm2

# Verify installation
pm2 --version

# Setup PM2 startup script (runs on server reboot)
pm2 startup
# Follow the command it outputs (sudo env PATH=...)

# Save PM2 process list
pm2 save

5. Install MySQL 8.x

# Install MySQL Server
sudo apt install -y mysql-server

# Secure MySQL installation
sudo mysql_secure_installation

# Follow prompts:
# - Set root password
# - Remove anonymous users: Yes
# - Disallow root login remotely: Yes
# - Remove test database: Yes
# - Reload privilege tables: Yes

# Verify installation
sudo systemctl status mysql
mysql --version  # Should show 8.0.x

6. Install Redis 7.x

# Install Redis
sudo apt install -y redis-server

# Enable Redis to start on boot
sudo systemctl enable redis-server

# Start Redis
sudo systemctl start redis-server

# Verify installation
redis-cli ping  # Should return "PONG"

7. Install NGINX

# Install NGINX
sudo apt install -y nginx

# Enable NGINX to start on boot
sudo systemctl enable nginx

# Start NGINX
sudo systemctl start nginx

# Verify installation
sudo systemctl status nginx
nginx -v  # Should show nginx version

# Test NGINX
curl http://localhost  # Should show NGINX welcome page

Application Setup

1. Create Application User

# Create user without login shell (security)
sudo useradd -r -s /bin/false mmr-app

# Or create user with bash (for debugging)
sudo useradd -m -s /bin/bash mmr-app

# Switch to application user
sudo su - mmr-app

2. Clone Repository

# Navigate to home directory
cd ~

# Clone repository (use HTTPS or SSH)
git clone <repository-url> mmr-saas
cd mmr-saas

# Checkout specific branch/tag
git checkout main  # or develop for staging

3. Install Dependencies

# Install all dependencies
pnpm install

# Verify installation
pnpm list --depth=0

4. Configure Environment Variables

Backend Environment

# Create .env file from example
cp apps/backend/.env.example apps/backend/.env

# Edit environment file
nano apps/backend/.env

Backend .env Configuration:

# Server Configuration
NODE_ENV=staging
PORT=3001
API_PREFIX=api

# Database
DB_HOST=localhost
DB_PORT=3306
DB_USERNAME=mmr_user
DB_PASSWORD=your-secure-database-password
DB_NAME=mmr_saas_staging
DB_SYNCHRONIZE=false  # Always false in production/staging

# Redis
REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_PASSWORD=  # Leave empty if no password

# JWT Secrets (generate strong secrets)
# Generate with: node -e "console.log(require('crypto').randomBytes(64).toString('hex'))"
JWT_SECRET=your-super-secret-jwt-key-64-characters-or-more
JWT_EXPIRES_IN=15m
JWT_REFRESH_SECRET=your-super-secret-refresh-key-64-characters-or-more
JWT_REFRESH_EXPIRY=7d

# CORS
CORS_ORIGIN=https://staging.yourdomain.com

# Rate Limiting
RATE_LIMIT_LOGIN_ATTEMPTS=5
RATE_LIMIT_LOCKOUT_MINUTES=15

# Logging
LOG_LEVEL=info  # debug | info | warn | error

Frontend Environment

# Create .env.local file
cp apps/web/.env.example apps/web/.env.local

# Edit environment file
nano apps/web/.env.local

Frontend .env.local Configuration:

# Backend API URL
NEXT_PUBLIC_API_URL=https://api.staging.yourdomain.com

# Environment
NEXT_PUBLIC_ENV=staging

# Analytics (optional)
NEXT_PUBLIC_GA_ID=  # Google Analytics ID

5. Setup Database

# Login to MySQL
mysql -u root -p

# Create database
CREATE DATABASE mmr_saas_staging CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

# Create user with strong password
CREATE USER 'mmr_user'@'localhost' IDENTIFIED BY 'your-secure-database-password';

# Grant privileges
GRANT ALL PRIVILEGES ON mmr_saas_staging.* TO 'mmr_user'@'localhost';
FLUSH PRIVILEGES;

# Exit MySQL
EXIT;

# Test connection
mysql -u mmr_user -p mmr_saas_staging

6. Run Database Migrations

# Navigate to project directory
cd ~/mmr-saas

# Run migrations
pnpm db:migrate

# Verify migrations
pnpm migration:show

# (Optional) Seed test data for staging
pnpm db:seed

7. Build Applications

# Build both frontend and backend
pnpm build

# Or build separately
pnpm build:back  # Backend only
pnpm build:web   # Frontend only

# Verify build output
ls -la apps/backend/dist
ls -la apps/web/.next

PM2 Process Management

1. Create PM2 Ecosystem Config

Create ecosystem.config.js in project root:

nano ~/mmr-saas/ecosystem.config.js

ecosystem.config.js:

module.exports = {
  apps: [
    {
      name: 'mmr-backend',
      cwd: '/home/mmr-app/mmr-saas/apps/backend',
      script: 'dist/main.js',
      instances: 2,  // Number of instances (CPU cores)
      exec_mode: 'cluster',
      env: {
        NODE_ENV: 'staging',
        PORT: 3001,
      },
      error_file: '/home/mmr-app/logs/backend-error.log',
      out_file: '/home/mmr-app/logs/backend-out.log',
      log_date_format: 'YYYY-MM-DD HH:mm:ss Z',
      merge_logs: true,
      autorestart: true,
      max_memory_restart: '1G',
      watch: false,
    },
    {
      name: 'mmr-frontend',
      cwd: '/home/mmr-app/mmr-saas/apps/web',
      script: 'node_modules/next/dist/bin/next',
      args: 'start -p 3000',
      instances: 1,
      exec_mode: 'cluster',
      env: {
        NODE_ENV: 'production',
        PORT: 3000,
      },
      error_file: '/home/mmr-app/logs/frontend-error.log',
      out_file: '/home/mmr-app/logs/frontend-out.log',
      log_date_format: 'YYYY-MM-DD HH:mm:ss Z',
      merge_logs: true,
      autorestart: true,
      max_memory_restart: '1G',
      watch: false,
    },
  ],
};

2. Create Log Directory

# Create logs directory
mkdir -p ~/logs

# Set permissions
chmod 755 ~/logs

3. Start Applications with PM2

# Start all applications
pm2 start ecosystem.config.js

# Or start individually
pm2 start ecosystem.config.js --only mmr-backend
pm2 start ecosystem.config.js --only mmr-frontend

# View running processes
pm2 list

# View logs
pm2 logs

# View logs for specific app
pm2 logs mmr-backend
pm2 logs mmr-frontend --lines 100

# Monitor processes
pm2 monit

4. PM2 Commands Reference

# Process Management
pm2 start ecosystem.config.js     # Start all apps
pm2 stop all                       # Stop all apps
pm2 restart all                    # Restart all apps
pm2 reload all                     # Graceful reload (zero downtime)
pm2 delete all                     # Delete all apps

# Individual App Control
pm2 stop mmr-backend
pm2 restart mmr-backend
pm2 reload mmr-backend

# Logs
pm2 logs                           # All logs (live tail)
pm2 logs mmr-backend --lines 200   # Last 200 lines
pm2 logs --err                     # Error logs only
pm2 flush                          # Clear logs

# Monitoring
pm2 monit                          # Real-time monitoring
pm2 status                         # Process status
pm2 describe mmr-backend           # Detailed info

# Startup
pm2 startup                        # Generate startup script
pm2 save                           # Save current process list
pm2 resurrect                      # Restore saved processes

# Updates
pm2 update                         # Update PM2 daemon

NGINX Configuration

1. Create NGINX Configuration File

# Create new site configuration
sudo nano /etc/nginx/sites-available/mmr-saas

NGINX Configuration:

# Upstream definitions
upstream mmr_backend {
    least_conn;  # Load balancing method
    server localhost:3001;
    # Add more backend instances if using multiple servers
    # server localhost:3002;
}

upstream mmr_frontend {
    server localhost:3000;
}

# Redirect HTTP to HTTPS (will be uncommented after SSL setup)
# server {
#     listen 80;
#     listen [::]:80;
#     server_name staging.yourdomain.com api.staging.yourdomain.com;
#     return 301 https://$server_name$request_uri;
# }

# Frontend Server (HTTPS)
server {
    listen 80;
    # listen 443 ssl http2;  # Uncomment after SSL setup
    # listen [::]:443 ssl http2;
    server_name staging.yourdomain.com;

    # SSL Configuration (uncomment after certbot)
    # ssl_certificate /etc/letsencrypt/live/staging.yourdomain.com/fullchain.pem;
    # ssl_certificate_key /etc/letsencrypt/live/staging.yourdomain.com/privkey.pem;
    # include /etc/letsencrypt/options-ssl-nginx.conf;
    # ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;

    # Security Headers
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-XSS-Protection "1; mode=block" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;

    # Logging
    access_log /var/log/nginx/mmr-frontend-access.log;
    error_log /var/log/nginx/mmr-frontend-error.log;

    # Gzip Compression
    gzip on;
    gzip_vary on;
    gzip_min_length 1024;
    gzip_types text/plain text/css text/xml text/javascript application/json application/javascript application/xml+rss;

    # Proxy to Next.js
    location / {
        proxy_pass http://mmr_frontend;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        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_cache_bypass $http_upgrade;
        proxy_read_timeout 60s;
        proxy_connect_timeout 60s;
    }

    # Static files caching
    location /_next/static {
        proxy_pass http://mmr_frontend;
        proxy_cache_valid 200 60m;
        add_header Cache-Control "public, max-age=3600, immutable";
    }
}

# Backend API Server (HTTPS)
server {
    listen 80;
    # listen 443 ssl http2;  # Uncomment after SSL setup
    # listen [::]:443 ssl http2;
    server_name api.staging.yourdomain.com;

    # SSL Configuration (uncomment after certbot)
    # ssl_certificate /etc/letsencrypt/live/api.staging.yourdomain.com/fullchain.pem;
    # ssl_certificate_key /etc/letsencrypt/live/api.staging.yourdomain.com/privkey.pem;
    # include /etc/letsencrypt/options-ssl-nginx.conf;
    # ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;

    # Security Headers
    add_header X-Frame-Options "DENY" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-XSS-Protection "1; mode=block" always;

    # Logging
    access_log /var/log/nginx/mmr-backend-access.log;
    error_log /var/log/nginx/mmr-backend-error.log;

    # Gzip Compression
    gzip on;
    gzip_vary on;
    gzip_types application/json text/plain;

    # Proxy to NestJS Backend
    location / {
        proxy_pass http://mmr_backend;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        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_cache_bypass $http_upgrade;
        proxy_read_timeout 300s;
        proxy_connect_timeout 300s;
        proxy_send_timeout 300s;

        # CORS headers (if needed)
        # add_header 'Access-Control-Allow-Origin' 'https://staging.yourdomain.com' always;
        # add_header 'Access-Control-Allow-Credentials' 'true' always;
        # add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always;
        # add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type' always;
    }

    # Health check endpoint (public)
    location /health {
        proxy_pass http://mmr_backend/api/health;
        access_log off;
    }
}

2. Enable Site and Test Configuration

# Create symbolic link to enable site
sudo ln -s /etc/nginx/sites-available/mmr-saas /etc/nginx/sites-enabled/

# Remove default site (optional)
sudo rm /etc/nginx/sites-enabled/default

# Test NGINX configuration
sudo nginx -t

# If test passes, reload NGINX
sudo systemctl reload nginx

3. Configure DNS

Point your domain to the server:

A Record:  staging.yourdomain.com         → your-server-ip
A Record:  api.staging.yourdomain.com     → your-server-ip

Wait for DNS propagation (5-30 minutes).

4. Install SSL Certificate (Let's Encrypt)

# Install Certbot
sudo apt install -y certbot python3-certbot-nginx

# Obtain SSL certificates
sudo certbot --nginx -d staging.yourdomain.com -d api.staging.yourdomain.com

# Follow prompts:
# - Enter email address
# - Agree to terms
# - Choose: Redirect HTTP to HTTPS (option 2)

# Certbot will automatically update NGINX config

# Test auto-renewal
sudo certbot renew --dry-run

# Auto-renewal is already configured via systemd timer
sudo systemctl status certbot.timer

5. Verify SSL

# Test HTTPS
curl https://staging.yourdomain.com
curl https://api.staging.yourdomain.com/api/health

# Check SSL certificate
openssl s_client -connect staging.yourdomain.com:443 -showcerts

Database Configuration

1. MySQL Performance Tuning

# Edit MySQL config
sudo nano /etc/mysql/mysql.conf.d/mysqld.cnf

Add/Update:

[mysqld]
# Performance tuning
max_connections = 200
innodb_buffer_pool_size = 2G  # 50-70% of RAM
innodb_log_file_size = 256M
innodb_flush_log_at_trx_commit = 2
query_cache_size = 0
query_cache_type = 0

# Logging
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow-query.log
long_query_time = 2
# Restart MySQL
sudo systemctl restart mysql

2. Database Backups

Create backup script:

nano ~/backup-db.sh

backup-db.sh:

#!/bin/bash

BACKUP_DIR="/home/mmr-app/backups/mysql"
DATE=$(date +%Y%m%d_%H%M%S)
DB_NAME="mmr_saas_staging"
DB_USER="mmr_user"
DB_PASS="your-secure-database-password"

# Create backup directory
mkdir -p $BACKUP_DIR

# Dump database
mysqldump -u $DB_USER -p$DB_PASS $DB_NAME | gzip > $BACKUP_DIR/mmr_saas_$DATE.sql.gz

# Keep only last 7 days of backups
find $BACKUP_DIR -name "*.sql.gz" -type f -mtime +7 -delete

echo "Backup completed: $BACKUP_DIR/mmr_saas_$DATE.sql.gz"
# Make executable
chmod +x ~/backup-db.sh

# Test backup
./backup-db.sh

# Schedule daily backups (cron)
crontab -e

# Add line:
0 2 * * * /home/mmr-app/backup-db.sh >> /home/mmr-app/logs/backup.log 2>&1

Security Hardening

1. Firewall (UFW)

# Install UFW
sudo apt install -y ufw

# Allow SSH (IMPORTANT: Do this first!)
sudo ufw allow 22/tcp

# Allow HTTP and HTTPS
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp

# Enable firewall
sudo ufw enable

# Check status
sudo ufw status verbose

# Application will be accessible only via NGINX (ports 80/443)
# PM2 apps (3000, 3001) are not exposed directly

2. Fail2Ban (Brute Force Protection)

# Install Fail2Ban
sudo apt install -y fail2ban

# Create local configuration
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local

# Edit configuration
sudo nano /etc/fail2ban/jail.local

Add NGINX protection:

[nginx-http-auth]
enabled = true

[nginx-limit-req]
enabled = true
port = http,https
logpath = /var/log/nginx/*error.log

[nginx-botsearch]
enabled = true
port = http,https
logpath = /var/log/nginx/*access.log
# Restart Fail2Ban
sudo systemctl restart fail2ban

# Check status
sudo fail2ban-client status

3. SSH Hardening

# Edit SSH config
sudo nano /etc/ssh/sshd_config

Recommended changes:

# Disable root login
PermitRootLogin no

# Use SSH keys only (disable password auth)
PasswordAuthentication no
ChallengeResponseAuthentication no

# Change default port (optional)
Port 2222

# Allow specific users only
AllowUsers mmr-app yourusername
# Restart SSH
sudo systemctl restart sshd

# IMPORTANT: Test new SSH connection in another terminal before closing current session!

4. Automatic Security Updates

# Install unattended-upgrades
sudo apt install -y unattended-upgrades

# Enable automatic updates
sudo dpkg-reconfigure -plow unattended-upgrades

# Select "Yes" to enable

Monitoring

1. PM2 Monitoring

# Real-time monitoring
pm2 monit

# Process status
pm2 status

# Detailed app info
pm2 describe mmr-backend

# System info
pm2 info

# View logs
pm2 logs --lines 100

2. Application Logs

# Backend logs
tail -f /home/mmr-app/logs/backend-out.log
tail -f /home/mmr-app/logs/backend-error.log

# Frontend logs
tail -f /home/mmr-app/logs/frontend-out.log

# NGINX logs
sudo tail -f /var/log/nginx/mmr-backend-access.log
sudo tail -f /var/log/nginx/mmr-frontend-error.log

3. System Monitoring

# Install htop (better than top)
sudo apt install -y htop

# Monitor system resources
htop

# Disk usage
df -h

# Memory usage
free -h

# Process tree
pstree -p

# Network connections
netstat -tulpn

# Or use ss
ss -tulpn

4. Health Checks

Create health check script:

nano ~/health-check.sh

health-check.sh:

#!/bin/bash

# Check backend health
BACKEND=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:3001/api/health)
if [ "$BACKEND" -eq 200 ]; then
    echo "Backend: OK"
else
    echo "Backend: FAILED (HTTP $BACKEND)"
    # Restart backend
    pm2 restart mmr-backend
fi

# Check frontend
FRONTEND=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:3000)
if [ "$FRONTEND" -eq 200 ] || [ "$FRONTEND" -eq 304 ]; then
    echo "Frontend: OK"
else
    echo "Frontend: FAILED (HTTP $FRONTEND)"
    # Restart frontend
    pm2 restart mmr-frontend
fi

# Check MySQL
if mysqladmin ping -u mmr_user -pyour-password &> /dev/null; then
    echo "MySQL: OK"
else
    echo "MySQL: FAILED"
    sudo systemctl restart mysql
fi

# Check Redis
if redis-cli ping &> /dev/null; then
    echo "Redis: OK"
else
    echo "Redis: FAILED"
    sudo systemctl restart redis-server
fi
# Make executable
chmod +x ~/health-check.sh

# Schedule every 5 minutes (cron)
crontab -e

# Add line:
*/5 * * * * /home/mmr-app/health-check.sh >> /home/mmr-app/logs/health.log 2>&1

Backup Strategies

1. Database Backups

Already configured in Database Configuration section.

2. Application File Backups

# Create backup directory
mkdir -p ~/backups/files

# Backup entire application (excluding node_modules)
tar --exclude='node_modules' --exclude='.git' \
    -czf ~/backups/files/mmr-saas-$(date +%Y%m%d).tar.gz \
    ~/mmr-saas

# Automate (add to cron)
crontab -e

# Add line (daily at 3 AM):
0 3 * * * tar --exclude='node_modules' --exclude='.git' -czf ~/backups/files/mmr-saas-$(date +\%Y\%m\%d).tar.gz ~/mmr-saas

3. Off-Site Backups

# Install rclone for cloud backups
curl https://rclone.org/install.sh | sudo bash

# Configure rclone (AWS S3, Google Drive, etc.)
rclone config

# Sync backups to cloud
rclone sync ~/backups remote:mmr-backups

# Automate (add to cron)
0 4 * * * rclone sync ~/backups remote:mmr-backups

Deployment Workflow

Deploying Updates

# 1. SSH to server
ssh mmr-app@your-server-ip

# 2. Navigate to project
cd ~/mmr-saas

# 3. Pull latest code
git pull origin main  # or develop for staging

# 4. Install new dependencies (if any)
pnpm install

# 5. Run database migrations (if any)
pnpm db:migrate

# 6. Build applications
pnpm build

# 7. Reload PM2 processes (zero downtime)
pm2 reload all

# 8. Verify deployment
curl http://localhost:3001/api/health
curl http://localhost:3000

# 9. Check logs
pm2 logs --lines 50

Rollback Procedure

# 1. Find previous commit
git log --oneline -5

# 2. Revert to previous commit
git reset --hard <previous-commit-hash>

# 3. Rebuild
pnpm build

# 4. Reload PM2
pm2 reload all

# Or use git revert for safer rollback
git revert <bad-commit-hash>
pnpm build
pm2 reload all

Troubleshooting

PM2 Process Not Starting

# Check PM2 logs
pm2 logs mmr-backend --err --lines 100

# Check if port is in use
sudo lsof -i :3001
sudo lsof -i :3000

# Kill process using port
sudo kill -9 <PID>

# Restart PM2
pm2 delete all
pm2 start ecosystem.config.js

Database Connection Errors

# Check MySQL is running
sudo systemctl status mysql

# Check connection
mysql -u mmr_user -p mmr_saas_staging

# Check credentials in .env
cat apps/backend/.env | grep DB_

# Check MySQL logs
sudo tail -f /var/log/mysql/error.log

NGINX Configuration Errors

# Test configuration
sudo nginx -t

# Check syntax errors
sudo nginx -T

# Reload NGINX
sudo systemctl reload nginx

# Check NGINX logs
sudo tail -f /var/log/nginx/error.log

SSL Certificate Issues

# Test certificate renewal
sudo certbot renew --dry-run

# Force renewal
sudo certbot renew --force-renewal

# Check certificate expiry
sudo certbot certificates

# NGINX reload after renewal
sudo systemctl reload nginx

Out of Memory Errors

# Check memory usage
free -h

# Check PM2 memory limits
pm2 list

# Adjust PM2 memory limit in ecosystem.config.js
max_memory_restart: '2G'  # Increase from 1G

# Add swap space (if needed)
sudo fallocate -l 4G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile

# Make swap permanent
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab

High CPU Usage

# Check processes
htop

# Check PM2 CPU usage
pm2 monit

# Reduce PM2 instances
# Edit ecosystem.config.js: instances: 1

# Optimize Node.js
# Add to ecosystem.config.js:
node_args: '--max-old-space-size=2048'

Redis Connection Errors

# Check Redis is running
sudo systemctl status redis-server

# Test connection
redis-cli ping

# Check Redis logs
sudo tail -f /var/log/redis/redis-server.log

# Restart Redis
sudo systemctl restart redis-server

Quick Reference

Common PM2 Commands

pm2 start ecosystem.config.js     # Start all apps
pm2 reload all                     # Zero-downtime reload
pm2 restart all                    # Hard restart
pm2 stop all                       # Stop all apps
pm2 logs                           # View logs
pm2 monit                          # Monitor
pm2 save                           # Save process list

Common NGINX Commands

sudo nginx -t                      # Test config
sudo systemctl reload nginx        # Reload config
sudo systemctl restart nginx       # Restart NGINX
sudo tail -f /var/log/nginx/error.log  # View logs

Common MySQL Commands

sudo systemctl status mysql        # Check status
mysql -u mmr_user -p              # Login
sudo tail -f /var/log/mysql/error.log  # View logs

Version: 2.0.0 | Last Updated: 2026-06-20