Install and configure Node.js web applications with PM2 and reverse proxy

Beginner 25 min Apr 03, 2026 115 views
Ubuntu 24.04 Ubuntu 22.04 Debian 12 AlmaLinux 9 Rocky Linux 9 Fedora 41

Deploy production-ready Node.js applications using PM2 process manager and Nginx reverse proxy with SSL certificates, automatic startup, and comprehensive monitoring for high-availability web hosting.

Prerequisites

  • Root or sudo access
  • Domain name with DNS configured
  • Basic understanding of Node.js and npm

What this solves

This tutorial shows you how to deploy Node.js applications in production with proper process management, load balancing, and SSL termination. You'll use PM2 to manage Node.js processes with automatic restarts and clustering, while Nginx handles reverse proxying, SSL certificates, and static file serving for optimal performance.

Step-by-step installation

Update system packages

Start by updating your package manager to ensure you get the latest versions of all dependencies.

sudo apt update && sudo apt upgrade -y
sudo dnf update -y

Install Node.js and npm

Install Node.js using the NodeSource repository to get the latest LTS version with better security and performance.

curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash -
sudo apt install -y nodejs build-essential
curl -fsSL https://rpm.nodesource.com/setup_lts.x | sudo bash -
sudo dnf install -y nodejs gcc-c++ make

Install PM2 globally

PM2 is a production process manager for Node.js applications that provides clustering, monitoring, and automatic restarts.

sudo npm install -g pm2

Create application user

Create a dedicated user for running Node.js applications to improve security by avoiding root execution.

sudo useradd -m -s /bin/bash nodeapp
sudo mkdir -p /var/www/nodeapp
sudo chown nodeapp:nodeapp /var/www/nodeapp

Create sample Node.js application

Switch to the nodeapp user and create a simple Express.js application for testing the deployment setup.

sudo -u nodeapp bash
cd /var/www/nodeapp
npm init -y
npm install express helmet morgan

Create application code

Create a production-ready Express application with security headers and request logging.

const express = require('express');
const helmet = require('helmet');
const morgan = require('morgan');

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

// Security middleware
app.use(helmet());

// Request logging
app.use(morgan('combined'));

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

// Main route
app.get('/', (req, res) => {
  res.json({
    message: 'Node.js application running with PM2',
    environment: process.env.NODE_ENV || 'development',
    version: process.env.npm_package_version || '1.0.0'
  });
});

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

app.listen(port, () => {
  console.log(Server running on port ${port});
});

Configure PM2 ecosystem file

Create a PM2 configuration file to define application settings, clustering, and environment variables.

module.exports = {
  apps: [{
    name: 'nodeapp',
    script: './app.js',
    instances: 'max',
    exec_mode: 'cluster',
    env: {
      NODE_ENV: 'production',
      PORT: 3000
    },
    error_file: '/var/log/pm2/nodeapp-error.log',
    out_file: '/var/log/pm2/nodeapp-out.log',
    log_file: '/var/log/pm2/nodeapp.log',
    time: true,
    max_restarts: 10,
    min_uptime: '10s',
    max_memory_restart: '512M',
    watch: false,
    ignore_watch: ['node_modules', 'logs'],
    node_args: '--max-old-space-size=512'
  }]
};

Create PM2 log directory

Set up log directory with proper permissions for PM2 to write application logs.

exit
sudo mkdir -p /var/log/pm2
sudo chown nodeapp:nodeapp /var/log/pm2
sudo chmod 755 /var/log/pm2

Start application with PM2

Launch the Node.js application using PM2 with the ecosystem configuration file.

sudo -u nodeapp bash
cd /var/www/nodeapp
pm2 start ecosystem.config.js
pm2 save
exit

Configure PM2 startup script

Generate and install a systemd service to automatically start PM2 applications on server boot.

sudo -u nodeapp pm2 startup systemd -u nodeapp --hp /home/nodeapp
sudo systemctl enable pm2-nodeapp

Install and configure Nginx

Install Nginx to serve as a reverse proxy and handle SSL termination for the Node.js application.

sudo apt install -y nginx certbot python3-certbot-nginx
sudo dnf install -y nginx certbot python3-certbot-nginx

Configure Nginx virtual host

Create an Nginx server block to proxy requests to the PM2-managed Node.js application with security headers.

upstream nodeapp {
    server 127.0.0.1:3000;
    keepalive 32;
}

server {
    listen 80;
    server_name example.com www.example.com;
    
    # Security headers
    add_header X-Frame-Options DENY;
    add_header X-Content-Type-Options nosniff;
    add_header X-XSS-Protection "1; mode=block";
    add_header Referrer-Policy "strict-origin-when-cross-origin";
    
    # Rate limiting
    limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
    limit_req zone=api burst=20 nodelay;
    
    # Gzip compression
    gzip on;
    gzip_vary on;
    gzip_min_length 1000;
    gzip_types text/plain application/json application/javascript text/css;
    
    # Health check endpoint
    location /health {
        access_log off;
        proxy_pass http://nodeapp;
        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;
    }
    
    # Main application
    location / {
        proxy_pass http://nodeapp;
        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_connect_timeout 5s;
        proxy_send_timeout 60s;
        proxy_read_timeout 60s;
    }
    
    # Static file serving (if needed)
    location /static/ {
        alias /var/www/nodeapp/public/;
        expires 1y;
        add_header Cache-Control "public, immutable";
    }
}

Enable Nginx site

Enable the virtual host and configure Nginx to start on boot.

sudo ln -s /etc/nginx/sites-available/nodeapp /etc/nginx/sites-enabled/
sudo rm -f /etc/nginx/sites-enabled/default
sudo nginx -t
sudo systemctl enable --now nginx
sudo cp /etc/nginx/sites-available/nodeapp /etc/nginx/conf.d/nodeapp.conf
sudo nginx -t
sudo systemctl enable --now nginx

Configure SSL certificate

Use Certbot to obtain and configure SSL certificates for secure HTTPS connections.

Note: Replace example.com with your actual domain name. Ensure DNS is pointing to your server before running this command.
sudo certbot --nginx -d example.com -d www.example.com --non-interactive --agree-tos --email admin@example.com

Configure firewall

Open the necessary ports for HTTP and HTTPS traffic while maintaining security.

sudo ufw allow 'Nginx Full'
sudo ufw --force enable
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https
sudo firewall-cmd --reload

Configure log rotation

Set up log rotation for PM2 application logs to prevent disk space issues.

/var/log/pm2/*.log {
    daily
    missingok
    rotate 14
    compress
    delaycompress
    notifempty
    copytruncate
    su nodeapp nodeapp
}

Verify your setup

Test that all components are working correctly and the application is accessible.

# Check PM2 status
sudo -u nodeapp pm2 status
sudo -u nodeapp pm2 logs nodeapp --lines 10

Check Nginx status

sudo nginx -t sudo systemctl status nginx

Test application locally

curl http://localhost:3000/health

Test through Nginx proxy

curl -H "Host: example.com" http://localhost/health

Test SSL certificate (if configured)

curl -I https://example.com/health

Common issues

Symptom Cause Fix
PM2 app won't start File permissions or missing dependencies sudo -u nodeapp pm2 logs nodeapp and check ownership with ls -la /var/www/nodeapp
502 Bad Gateway Node.js app not running or port mismatch Verify PM2 status and check port 3000 with netstat -tlnp | grep 3000
SSL certificate error Domain not pointing to server Check DNS with dig example.com and retry certbot after DNS propagation
High memory usage Memory leaks or too many cluster instances Adjust instances and max_memory_restart in ecosystem.config.js
PM2 not starting on boot Startup script not properly configured Re-run sudo -u nodeapp pm2 startup systemd and check systemd status

Next steps

Automated install script

Run this to automate the entire setup

#nodejs #pm2 #nginx #reverse-proxy #ssl #production

Need help?

Don't want to manage this yourself?

We handle infrastructure for businesses that depend on uptime. From initial setup to ongoing operations.

Talk to an engineer