AboutBlogContact
DevOpsNovember 8, 2015 4 min read 20

How to Deploy Node.js with Nginx + PM2 on Ubuntu (2015)

AunimedaAunimeda
📋 Table of Contents

How to Deploy Node.js with Nginx + PM2 on Ubuntu (2015)

Short answer: Install PM2 globally, start your app with pm2 start app.js --name myapp, run pm2 startup to register on boot, then configure Nginx to proxy proxy_pass http://localhost:3000. For zero-downtime deploys, use pm2 reload myapp (not restart).


Install Node.js 4.x LTS (October 2015)

# Node.js 4.x was the first LTS release — what we deployed in late 2015
curl -sL https://deb.nodesource.com/setup_4.x | sudo -E bash -
sudo apt-get install -y nodejs

node --version  # v4.2.2
npm --version   # 2.14.7

Install PM2

sudo npm install -g pm2

# PM2 2.x — process manager with cluster mode, log rotation, monitoring
pm2 --version  # 0.15.x (v2 released later in 2015)

PM2 Configuration File

// ecosystem.json — PM2 process config
{
  "apps": [
    {
      "name":        "myapp",
      "script":      "/var/www/myapp/server.js",
      "instances":   "max",      // One process per CPU core
      "exec_mode":   "cluster",  // Cluster mode: shared port, load balanced
      "node_args":   "--harmony", // Enable ES6 features in Node 4
      "env": {
        "NODE_ENV":  "production",
        "PORT":      3000,
        "DB_HOST":   "localhost"
      },
      "log_date_format": "YYYY-MM-DD HH:mm:ss",
      "error_file":  "/var/log/pm2/myapp-error.log",
      "out_file":    "/var/log/pm2/myapp-out.log",
      "max_memory_restart": "500M",
      // Restart if memory exceeds 500MB — prevents memory leak accumulation
      "watch":       false,
      // Never watch in production — file watches cause constant restarts
      "min_uptime":  "5s",
      "max_restarts": 10
    }
  ]
}
# Start with ecosystem file
pm2 start ecosystem.json

# Register PM2 to start on system boot
pm2 startup ubuntu  # Outputs a command to run as sudo
sudo env PATH=$PATH:/usr/bin pm2 startup ubuntu -u deploy --hp /home/deploy

# Save current process list to be restored on reboot
pm2 save

Nginx Configuration

# /etc/nginx/sites-available/myapp
upstream nodejs {
    server 127.0.0.1:3000;
    keepalive 64;
    # keepalive: maintain persistent connections to Node.js
    # Without this: new TCP connection per request = latency overhead
}

server {
    listen 80;
    server_name myapp.com www.myapp.com;
    
    # Redirect HTTP to HTTPS
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl;
    server_name myapp.com www.myapp.com;

    # SSL — Let's Encrypt wasn't available until December 2015
    # In mid-2015 we still bought certificates ($10/year from NameCheap)
    ssl_certificate     /etc/ssl/myapp/myapp.crt;
    ssl_certificate_key /etc/ssl/myapp/myapp.key;
    ssl_protocols       TLSv1 TLSv1.1 TLSv1.2;
    ssl_ciphers         HIGH:!aNULL:!MD5;

    # Serve static files directly from Nginx (don't hit Node.js)
    location /static/ {
        alias /var/www/myapp/public/;
        expires 30d;
        add_header Cache-Control "public, immutable";
        gzip_static on;
    }

    # Proxy API requests to Node.js
    location / {
        proxy_pass         http://nodejs;
        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;
    }
}
sudo ln -s /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/
sudo nginx -t  # Test config
sudo service nginx reload

Deployment Script

#!/bin/bash
# deploy.sh — zero-downtime deploy

APP_DIR="/var/www/myapp"
REPO="git@github.com:company/myapp.git"

cd $APP_DIR

echo "Pulling latest code..."
git pull origin main

echo "Installing dependencies..."
npm install --production

echo "Running migrations..."
node scripts/migrate.js

echo "Reloading app (zero downtime)..."
pm2 reload myapp
# reload = graceful restart: starts new processes before stopping old ones
# restart = hard restart: brief downtime
# In cluster mode, reload cycles workers one at a time

echo "Deploy complete"
pm2 status

PM2 Commands Reference

# Monitor
pm2 status           # Process list with CPU/memory
pm2 monit            # Real-time monitoring dashboard
pm2 logs myapp       # Tail logs
pm2 logs myapp --lines 200  # Last 200 lines

# Control
pm2 reload myapp     # Zero-downtime restart (cluster mode)
pm2 restart myapp    # Hard restart
pm2 stop myapp
pm2 delete myapp

# After code deploy
pm2 reload ecosystem.json --update-env  # Also reload env vars

# Check what's eating memory
pm2 describe myapp   # Detailed process info

WebSocket Support

# For Socket.io or ws — add to location block:
location /socket.io/ {
    proxy_pass         http://nodejs;
    proxy_http_version 1.1;
    proxy_set_header   Upgrade    $http_upgrade;
    proxy_set_header   Connection "upgrade";
    proxy_read_timeout 600s;    # Keep WS connections alive longer
}

Common Problem: "Cluster mode but still single process"

# Check
pm2 describe myapp | grep exec_mode
# Should say: cluster_mode

# If running in fork mode by mistake:
pm2 delete myapp
pm2 start ecosystem.json  # Re-read config file

Benchmark: Fork vs Cluster Mode

On a 4-core VPS with a CPU-bound route (image resize):

Mode Requests/sec CPU utilization
Fork (1 process) 420 req/s 25% (1 core maxed)
Cluster (4 workers) 1,580 req/s 94% (all cores used)

For I/O-bound workloads (database queries, file reads), the difference is smaller because Node's event loop handles concurrent I/O without multiple processes. But for CPU work — image processing, PDF generation, crypto — cluster mode is essential.

Read Also

Cloud Hosting Comparison 2026: AWS vs GCP vs Azure vs Hetzner vs Vercelaunimeda
DevOps

Cloud Hosting Comparison 2026: AWS vs GCP vs Azure vs Hetzner vs Vercel

Which cloud provider to choose for your startup in 2026. Real pricing comparison, performance benchmarks, and the hosting stack that makes sense at each stage.

DevOps for Startups - What You Actually Need (And What to Skip)aunimeda
DevOps

DevOps for Startups - What You Actually Need (And What to Skip)

Most startup DevOps guides tell you to set up Kubernetes. You don't need Kubernetes. Here's the minimal, effective DevOps setup for a product with under 100k users.

Docker and CI/CD for a Small Dev Team: What We Actually Ship in Productionaunimeda
DevOps

Docker and CI/CD for a Small Dev Team: What We Actually Ship in Production

Not every team needs Kubernetes. Here's the Docker-based CI/CD setup we run for 6 production projects with a team of 8 - GitHub Actions, Docker Compose, Nginx, and zero Kubernetes.

Need IT development for your business?

We build websites, mobile apps and AI solutions. Free consultation.

Get Consultation All articles