DEV Community

Abdallateef Shohdy
Abdallateef Shohdy

Posted on

πŸš€ Deploy Node.js Apps on AWS EC2 - Complete Guide

πŸ“‘ Table of Contents

  1. Architecture Overview
  2. Request Flow Explained
  3. systemd vs PM2
  4. Step by Step Deployment Guide
  5. Process Management Deep Dive
  6. Monitoring and Logs
  7. Troubleshooting and Best Practices
  8. Summary

Architecture Overview

Complete Server Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                    Internet (External World)            β”‚
β”‚                   https://yourdomain.com                β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                         β”‚
                         β”‚ DNS Resolution
                         β”‚
                         β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚              AWS EC2 Instance (Ubuntu)                  β”‚
β”‚              Public IP: xx.xx.xx.xx                     β”‚
β”‚                                                         β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”‚
β”‚  β”‚         Caddy (Reverse Proxy)                  β”‚     β”‚
β”‚  β”‚         Port 80 (HTTP) β†’ 443 (HTTPS)           β”‚     β”‚
β”‚  β”‚         βœ… Auto SSL Certificate                β”‚     β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β”‚
β”‚                 β”‚                 β”‚                     β”‚
β”‚                 β”‚                 β”‚                     β”‚
β”‚     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€-┐      β”‚
β”‚     β”‚   Frontend (Next.js) β”‚  β”‚  Backend (Node)  β”‚       β”‚
β”‚     β”‚   Port: 3000         β”‚  β”‚  Port: 5000      β”‚      β”‚
β”‚     β”‚   Managed by:        β”‚  β”‚  Managed by:     β”‚      β”‚
β”‚     β”‚   β€’ systemd OR       β”‚  β”‚  β€’ systemd OR    β”‚      β”‚
β”‚     β”‚   β€’ PM2              β”‚  β”‚  β€’ PM2           β”‚      β”‚
β”‚     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜      β”‚
β”‚                                                         β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”‚
β”‚  β”‚         Operating System Layer                 β”‚     β”‚
β”‚  β”‚         β€’ systemd (init system)                β”‚     β”‚
β”‚  β”‚         β€’ Process monitoring                   β”‚     β”‚
β”‚  β”‚         β€’ Auto-restart on crash                β”‚     β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
Enter fullscreen mode Exit fullscreen mode

Key Components Explained

Component Purpose Port Technology
Caddy Reverse Proxy & SSL 80, 443 Go-based web server
Frontend User Interface 3000 Next.js / React
Backend API / Business Logic 5000 Node.js / Express
systemd/PM2 Process Manager N/A Linux service / Node tool

Request Flow Explained

How a Request Travels Through Your Server

Step 1: User Request
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   User Browser  β”‚  User types: https://yourdomain.com
β”‚   (Chrome/etc)  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜
         β”‚
         β”‚ β‘  DNS Lookup (domain β†’ IP address)
         β”‚
         β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   DNS Server    β”‚  Returns: 54.123.45.67 (EC2 Public IP)
β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜
         β”‚
         β”‚ β‘‘ HTTPS Request to IP:443
         β”‚
         β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚         AWS EC2 Instance                β”‚
β”‚                                         β”‚
β”‚  Step 3: Caddy Receives Request         β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”‚
β”‚  β”‚  Caddy (Port 443)             β”‚     β”‚
β”‚  β”‚  β€’ Terminates SSL             β”‚     β”‚
β”‚  β”‚  β€’ Checks Caddyfile rules     β”‚     β”‚
β”‚  β”‚  β€’ Decides where to route     β”‚     β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β”‚
β”‚             β”‚                           β”‚
β”‚   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”               β”‚
β”‚   β”‚                    β”‚               β”‚
β”‚   β”‚ If route = /       β”‚ If route = /apiβ”‚
β”‚   β”‚                    β”‚               β”‚
β”‚   β–Ό                    β–Ό               β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”      β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”         β”‚
β”‚  β”‚Frontend β”‚      β”‚Backend  β”‚         β”‚
β”‚  β”‚localhostβ”‚      β”‚localhostβ”‚         β”‚
β”‚  β”‚:3000    β”‚      β”‚:5000    β”‚         β”‚
β”‚  β”‚         β”‚      β”‚         β”‚         β”‚
β”‚  β”‚Next.js  β”‚      β”‚Node.js  β”‚         β”‚
β”‚  β”‚App      β”‚      β”‚Express  β”‚         β”‚
β”‚  β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”˜      β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”˜         β”‚
β”‚       β”‚                β”‚               β”‚
β”‚       β”‚ β‘£ Process      β”‚ β‘£ Process     β”‚
β”‚       β”‚   Request      β”‚   Request     β”‚
β”‚       β”‚                β”‚               β”‚
β”‚       β–Ό                β–Ό               β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”      β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”         β”‚
β”‚  β”‚HTML/CSS/β”‚      β”‚JSON     β”‚         β”‚
β”‚  β”‚JS       β”‚      β”‚Response β”‚         β”‚
β”‚  β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”˜      β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”˜         β”‚
β”‚       β”‚                β”‚               β”‚
β”‚       β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜               β”‚
β”‚                β”‚                       β”‚
β”‚  β‘€ Response    β”‚                       β”‚
β”‚  back through  β”‚                       β”‚
β”‚  Caddy         β”‚                       β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”        β”‚
β”‚  β”‚  Caddy adds headers       β”‚        β”‚
β”‚  β”‚  β€’ Security headers       β”‚        β”‚
β”‚  β”‚  β€’ Compression           β”‚        β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜        β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                 β”‚
                 β”‚ β‘₯ HTTPS Response
                 β”‚
                 β–Ό
         β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
         β”‚   User Browser  β”‚  Renders the page
         β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
Enter fullscreen mode Exit fullscreen mode

Request Flow Examples

Example 1: Frontend Request

User β†’ https://yourdomain.com
     ↓
Caddy receives on :443
     ↓
Caddyfile rule: yourdomain.com {
    reverse_proxy localhost:3000
}
     ↓
Forwards to Next.js on localhost:3000
     ↓
Next.js returns HTML
     ↓
Caddy sends response back to user
Enter fullscreen mode Exit fullscreen mode

Example 2: API Request

User β†’ https://api.yourdomain.com/users
     ↓
Caddy receives on :443
     ↓
Caddyfile rule: api.yourdomain.com {
    reverse_proxy localhost:5000
}
     ↓
Forwards to Node.js backend on localhost:5000
     ↓
Express processes /users endpoint
     ↓
Returns JSON data
     ↓
Caddy sends response back to user
Enter fullscreen mode Exit fullscreen mode

systemd vs PM2

What is systemd?

systemd is the init system for Linux. It's responsible for:

  • Starting services when the server boots
  • Managing all system processes
  • Monitoring and restarting crashed processes
  • Managing system logs
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚     Linux Boot Process              β”‚
β”‚                                     β”‚
β”‚  β‘  Kernel loads                     β”‚
β”‚       ↓                             β”‚
β”‚  β‘‘ systemd starts (PID 1)           β”‚
β”‚       ↓                             β”‚
β”‚  β‘’ systemd reads service files      β”‚
β”‚     /etc/systemd/system/*.service   β”‚
β”‚       ↓                             β”‚
β”‚  β‘£ Starts enabled services          β”‚
β”‚     β€’ backend.service               β”‚
β”‚     β€’ frontend.service              β”‚
β”‚     β€’ caddy.service                 β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
Enter fullscreen mode Exit fullscreen mode

What is PM2?

PM2 is a process manager specifically for Node.js applications. Features:

  • Cluster mode (multi-core CPU usage)
  • Zero-downtime reload
  • Built-in load balancer
  • Rich monitoring dashboard
  • Log management
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚     PM2 Architecture                β”‚
β”‚                                     β”‚
β”‚  PM2 Daemon (God Process)           β”‚
β”‚       β”‚                             β”‚
β”‚       β”œβ”€β”€ App 1 (backend)           β”‚
β”‚       β”‚    β”œβ”€β”€ Instance 1           β”‚
β”‚       β”‚    β”œβ”€β”€ Instance 2           β”‚
β”‚       β”‚    └── Instance 3           β”‚
β”‚       β”‚                             β”‚
β”‚       └── App 2 (frontend)          β”‚
β”‚            └── Instance 1           β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
Enter fullscreen mode Exit fullscreen mode

Comparison Table

Feature systemd PM2
Platform Linux only Cross-platform
Language Any Node.js focused
Clustering Manual setup Built-in
Zero-downtime reload ❌ βœ…
Monitoring UI journalctl (CLI) Web dashboard
Memory usage Very low Moderate
Complexity Medium Easy
Auto-start on boot βœ… Native βœ… Via systemd
Log rotation βœ… Built-in βœ… Built-in
Process restart βœ… βœ…
Load balancing ❌ βœ…
Best for Production servers Node.js apps

When to Use What?

Use systemd when:

  • You want minimal dependencies
  • You're comfortable with Linux tools
  • You have non-Node.js services too
  • You want tight OS integration
  • You prefer standard Linux tools

Use PM2 when:

  • You need cluster mode (multi-core)
  • You want zero-downtime deployments
  • You need advanced monitoring
  • You want easier log management
  • Your team prefers Node.js tools

Use BOTH when:

  • You want PM2 features + systemd reliability
  • PM2 managed by systemd for auto-restart
  • Best of both worlds approach

Step by Step Deployment Guide

Prerequisites Checklist

  • [ ] AWS EC2 instance running Ubuntu 22.04+
  • [ ] SSH key for access
  • [ ] Domain name configured (optional but recommended)
  • [ ] Security Group allows ports: 22 (SSH), 80 (HTTP), 443 (HTTPS)
  • [ ] Git repositories ready (backend & frontend)

Part 1: Initial Server Setup

1.1 Connect to EC2

# From your local machine
ssh -i /path/to/your-key.pem ubuntu@your-ec2-ip

# Example:
ssh -i ~/aws-keys/my-app-key.pem ubuntu@54.123.45.67
Enter fullscreen mode Exit fullscreen mode

1.2 Update System

sudo apt update && sudo apt upgrade -y
Enter fullscreen mode Exit fullscreen mode

1.3 Install Essential Tools

sudo apt install -y git curl build-essential
Enter fullscreen mode Exit fullscreen mode

1.4 Configure Firewall

# Enable firewall
sudo ufw enable

# Allow SSH (important - don't lock yourself out!)
sudo ufw allow 22/tcp

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

# Check status
sudo ufw status
Enter fullscreen mode Exit fullscreen mode

Part 2: Install Node.js

# Install Node.js LTS (v20.x)
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt install -y nodejs

# Verify installation
node -v  # Should show v20.x.x
npm -v   # Should show 10.x.x
Enter fullscreen mode Exit fullscreen mode

Part 3: Clone and Setup Backend

3.1 Clone Repository

cd /home/ubuntu
git clone https://github.com/yourusername/backend-app.git
cd backend-app
Enter fullscreen mode Exit fullscreen mode

3.2 Create Environment File

nano .env
Enter fullscreen mode Exit fullscreen mode

Add your configuration:

PORT=5000
NODE_ENV=production

# Database
MONGO_URI=mongodb+srv://username:password@cluster.mongodb.net/dbname
# or
DATABASE_URL=postgresql://user:pass@host:5432/db

# Security
JWT_SECRET=your-super-secret-jwt-key-change-this
JWT_EXPIRE=7d

# API Keys
STRIPE_SECRET_KEY=sk_live_xxxxx
SENDGRID_API_KEY=SG.xxxxx

# CORS
ALLOWED_ORIGINS=https://yourdomain.com,https://www.yourdomain.com
Enter fullscreen mode Exit fullscreen mode

Security tip: Make sure .env is in .gitignore!

3.3 Install Dependencies

npm install --production
Enter fullscreen mode Exit fullscreen mode

3.4 Test Backend Locally

# Test run
npm start

# You should see something like:
# Server running on port 5000
Enter fullscreen mode Exit fullscreen mode

Press Ctrl + C to stop.


Part 4: Clone and Setup Frontend

4.1 Clone Repository

cd /home/ubuntu
git clone https://github.com/yourusername/frontend-app.git
cd frontend-app
Enter fullscreen mode Exit fullscreen mode

4.2 Create Environment File

nano .env.production
Enter fullscreen mode Exit fullscreen mode

Add your configuration:

# API Configuration
NEXT_PUBLIC_API_URL=https://api.yourdomain.com
NEXT_PUBLIC_API_TIMEOUT=10000

# App Configuration
NEXT_PUBLIC_APP_NAME=My Awesome App
NEXT_PUBLIC_APP_URL=https://yourdomain.com

# Analytics (optional)
NEXT_PUBLIC_GA_ID=G-XXXXXXXXXX

# Feature Flags
NEXT_PUBLIC_ENABLE_FEATURE_X=true
Enter fullscreen mode Exit fullscreen mode

4.3 Install and Build

npm install
npm run build

# This creates the .next folder with optimized production build
Enter fullscreen mode Exit fullscreen mode

4.4 Test Frontend Locally

npm start

# Should start on port 3000
Enter fullscreen mode Exit fullscreen mode

Press Ctrl + C to stop.


Part 5: Process Management - Option A (systemd)

5.1 Create Backend Service

sudo nano /etc/systemd/system/backend.service
Enter fullscreen mode Exit fullscreen mode

Add this configuration:

[Unit]
Description=Backend API Service
Documentation=https://github.com/yourusername/backend-app
After=network.target

[Service]
Type=simple
User=ubuntu
WorkingDirectory=/home/ubuntu/backend-app
EnvironmentFile=/home/ubuntu/backend-app/.env
ExecStart=/usr/bin/node /home/ubuntu/backend-app/src/server.js

# Restart policy
Restart=always
RestartSec=10

# Resource limits
LimitNOFILE=65536

# Security
NoNewPrivileges=true
PrivateTmp=true

# Logging
StandardOutput=journal
StandardError=journal
SyslogIdentifier=backend

[Install]
WantedBy=multi-user.target
Enter fullscreen mode Exit fullscreen mode

5.2 Create Frontend Service

sudo nano /etc/systemd/system/frontend.service
Enter fullscreen mode Exit fullscreen mode

Add this configuration:

[Unit]
Description=Frontend Next.js Application
Documentation=https://github.com/yourusername/frontend-app
After=network.target

[Service]
Type=simple
User=ubuntu
WorkingDirectory=/home/ubuntu/frontend-app
Environment="NODE_ENV=production"
ExecStart=/usr/bin/npm start

# Restart policy
Restart=always
RestartSec=10

# Resource limits
LimitNOFILE=65536

# Security
NoNewPrivileges=true
PrivateTmp=true

# Logging
StandardOutput=journal
StandardError=journal
SyslogIdentifier=frontend

[Install]
WantedBy=multi-user.target
Enter fullscreen mode Exit fullscreen mode

5.3 Enable and Start Services

# Reload systemd to read new service files
sudo systemctl daemon-reload

# Enable services (start on boot)
sudo systemctl enable backend
sudo systemctl enable frontend

# Start services now
sudo systemctl start backend
sudo systemctl start frontend

# Check status
sudo systemctl status backend
sudo systemctl status frontend
Enter fullscreen mode Exit fullscreen mode

5.4 Useful systemd Commands

# View logs
sudo journalctl -u backend -f        # Follow backend logs
sudo journalctl -u frontend -f       # Follow frontend logs
sudo journalctl -u backend --since "1 hour ago"
sudo journalctl -u backend -n 100    # Last 100 lines

# Service management
sudo systemctl restart backend       # Restart service
sudo systemctl stop backend          # Stop service
sudo systemctl start backend         # Start service
sudo systemctl disable backend       # Don't start on boot

# System status
systemctl list-units --type=service  # List all services
systemctl is-active backend          # Check if running
systemctl is-enabled backend         # Check if starts on boot
Enter fullscreen mode Exit fullscreen mode

Part 6: Process Management - Option B (PM2)

6.1 Install PM2 Globally

sudo npm install -g pm2
Enter fullscreen mode Exit fullscreen mode

6.2 Start Applications with PM2

# Start backend
cd /home/ubuntu/backend-app
pm2 start src/server.js --name backend

# Start frontend
cd /home/ubuntu/frontend-app
pm2 start npm --name frontend -- start

# Or for cluster mode (multiple instances):
pm2 start src/server.js --name backend -i max  # Uses all CPU cores
Enter fullscreen mode Exit fullscreen mode

6.3 Configure PM2 with ecosystem.config.js

Create a config file for better management:

cd /home/ubuntu
nano ecosystem.config.js
Enter fullscreen mode Exit fullscreen mode

Add this configuration:

module.exports = {
  apps: [
    {
      name: 'backend',
      cwd: '/home/ubuntu/backend-app',
      script: 'src/server.js',
      instances: 2,  // or 'max' for all CPU cores
      exec_mode: 'cluster',
      env: {
        NODE_ENV: 'production',
        PORT: 5000
      },
      error_file: '/home/ubuntu/logs/backend-error.log',
      out_file: '/home/ubuntu/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: 'frontend',
      cwd: '/home/ubuntu/frontend-app',
      script: 'npm',
      args: 'start',
      instances: 1,
      exec_mode: 'fork',
      env: {
        NODE_ENV: 'production',
        PORT: 3000
      },
      error_file: '/home/ubuntu/logs/frontend-error.log',
      out_file: '/home/ubuntu/logs/frontend-out.log',
      log_date_format: 'YYYY-MM-DD HH:mm:ss Z',
      merge_logs: true,
      autorestart: true,
      max_memory_restart: '500M',
      watch: false
    }
  ]
};
Enter fullscreen mode Exit fullscreen mode

6.4 Start with Ecosystem File

# Create logs directory
mkdir -p /home/ubuntu/logs

# Start all apps
pm2 start ecosystem.config.js

# Save PM2 process list
pm2 save

# Make PM2 start on system boot
pm2 startup systemd -u ubuntu --hp /home/ubuntu
# Run the command it outputs (starts with sudo)
Enter fullscreen mode Exit fullscreen mode

6.5 Useful PM2 Commands

# Process management
pm2 list                    # List all processes
pm2 status                  # Same as list
pm2 restart backend         # Restart specific app
pm2 restart all             # Restart all apps
pm2 reload backend          # Zero-downtime reload
pm2 stop backend            # Stop app
pm2 delete backend          # Remove app from PM2

# Monitoring
pm2 monit                   # Real-time monitoring dashboard
pm2 logs                    # View all logs
pm2 logs backend            # View specific app logs
pm2 logs backend --lines 100  # Last 100 lines
pm2 flush                   # Clear all logs

# Information
pm2 describe backend        # Detailed info about app
pm2 show backend            # Same as describe

# Cluster management
pm2 scale backend 4         # Scale to 4 instances
pm2 scale backend +2        # Add 2 more instances

# Updates
pm2 update                  # Update PM2 daemon
pm2 save                    # Save current process list
Enter fullscreen mode Exit fullscreen mode

Part 7: Install and Configure Caddy

7.1 Install Caddy

# Install dependencies
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https

# Add Caddy repository
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | \
  sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg

curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | \
  sudo tee /etc/apt/sources.list.d/caddy-stable.list

# Update and install
sudo apt update
sudo apt install caddy -y
Enter fullscreen mode Exit fullscreen mode

7.2 Configure Caddyfile

sudo nano /etc/caddy/Caddyfile
Enter fullscreen mode Exit fullscreen mode

Basic Configuration:

# Frontend
yourdomain.com {
    reverse_proxy localhost:3000

    # Optional: Compression
    encode gzip

    # Optional: Security headers
    header {
        Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
        X-Content-Type-Options "nosniff"
        X-Frame-Options "SAMEORIGIN"
        X-XSS-Protection "1; mode=block"
    }
}

# Backend API
api.yourdomain.com {
    reverse_proxy localhost:5000

    encode gzip

    header {
        Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
        X-Content-Type-Options "nosniff"
    }
}
Enter fullscreen mode Exit fullscreen mode

Advanced Configuration with Load Balancing:

# Frontend with multiple instances
yourdomain.com {
    reverse_proxy localhost:3000 localhost:3001 {
        lb_policy round_robin
        health_uri /
        health_interval 10s
    }

    encode gzip zstd

    header {
        Strict-Transport-Security "max-age=31536000"
        X-Content-Type-Options "nosniff"
    }

    # Rate limiting
    rate_limit {
        zone static_rl {
            key {remote_host}
            window 1m
            events 100
        }
    }
}

# Backend API with authentication
api.yourdomain.com {
    reverse_proxy localhost:5000 localhost:5001 {
        lb_policy least_conn
        health_uri /health
        health_interval 30s
    }

    encode gzip

    # CORS headers
    header {
        Access-Control-Allow-Origin "https://yourdomain.com"
        Access-Control-Allow-Methods "GET, POST, PUT, DELETE"
        Access-Control-Allow-Headers "Content-Type, Authorization"
    }
}
Enter fullscreen mode Exit fullscreen mode

7.3 Validate and Restart Caddy

# Validate configuration
sudo caddy validate --config /etc/caddy/Caddyfile

# If valid, reload Caddy
sudo systemctl reload caddy

# Check status
sudo systemctl status caddy

# View logs
sudo journalctl -u caddy -f
Enter fullscreen mode Exit fullscreen mode

7.4 Caddy will automatically:

  • Get SSL certificates from Let's Encrypt
  • Renew certificates before expiration
  • Redirect HTTP to HTTPS
  • Handle TLS termination

Process Management Deep Dive

How systemd Manages Processes

System Boot Sequence:
──────────────────────

β‘  BIOS/UEFI
    ↓
β‘‘ Bootloader (GRUB)
    ↓
β‘’ Linux Kernel loads
    ↓
β‘£ systemd starts (PID 1)
    ↓
β‘€ systemd reads target
   (multi-user.target)
    ↓
β‘₯ Starts all enabled services
   β€’ backend.service
   β€’ frontend.service
   β€’ caddy.service
   β€’ ssh.service
   β€’ etc.
Enter fullscreen mode Exit fullscreen mode

What happens when a process crashes?

Process Flow with systemd:
─────────────────────────

β‘  App running (PID 1234)
    ↓
β‘‘ App crashes (exit code 1)
    ↓
β‘’ systemd detects termination
    ↓
β‘£ Checks Restart= policy
   (Restart=always)
    ↓
β‘€ Waits RestartSec seconds (10s)
    ↓
β‘₯ Starts app again (new PID 1567)
    ↓
⑦ Logs event to journald
Enter fullscreen mode Exit fullscreen mode

How PM2 Manages Processes

PM2 Architecture:
────────────────

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   PM2 Daemon (God)       β”‚  ← Main PM2 process
β”‚   PID: 789               β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
         β”‚
         β”œβ”€β†’ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
         β”‚   β”‚ backend (cluster)β”‚
         β”‚   β”œβ”€β”€ Worker 1 (1234)β”‚
         β”‚   β”œβ”€β”€ Worker 2 (1235)β”‚
         β”‚   β”œβ”€β”€ Worker 3 (1236)β”‚
         β”‚   └── Worker 4 (1237)β”‚
         β”‚   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
         β”‚
         └─→ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
             β”‚ frontend (fork)  β”‚
             └── Process (1238) β”‚
             β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
Enter fullscreen mode Exit fullscreen mode

Cluster Mode Explained:

Without Cluster (1 instance):
────────────────────────────
CPU Core 1: [β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ] Backend running
CPU Core 2: [        ] Idle
CPU Core 3: [        ] Idle
CPU Core 4: [        ] Idle

With Cluster Mode (4 instances):
───────────────────────────────
CPU Core 1: [β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ] Backend Worker 1
CPU Core 2: [β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ] Backend Worker 2
CPU Core 3: [β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ] Backend Worker 3
CPU Core 4: [β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ] Backend Worker 4

Load Balancer distributes requests across all workers
Enter fullscreen mode Exit fullscreen mode

What happens when a PM2 process crashes?

Process Flow with PM2:
─────────────────────

β‘  Worker 2 crashes
    ↓
β‘‘ PM2 God process detects
    ↓
β‘’ Immediately spawns new worker
    ↓
β‘£ New worker starts in <1 second
    ↓
β‘€ Logs crash to PM2 logs
    ↓
β‘₯ Cluster continues serving traffic
   (Workers 1, 3, 4 still running)
Enter fullscreen mode Exit fullscreen mode

Monitoring and Logs

systemd Monitoring

Check Service Status

# Quick status check
sudo systemctl status backend
sudo systemctl status frontend

# Is the service running?
systemctl is-active backend   # Returns: active or inactive

# Will it start on boot?
systemctl is-enabled backend  # Returns: enabled or disabled
Enter fullscreen mode Exit fullscreen mode

View Logs

# View all logs for a service
sudo journalctl -u backend

# Follow logs in real-time (like tail -f)
sudo journalctl -u backend -f

# Show last 50 lines
sudo journalctl -u backend -n 50

# Show logs from last hour
sudo journalctl -u backend --since "1 hour ago"

# Show logs from specific time
sudo journalctl -u backend --since "2024-03-20 10:00:00"

# Show logs between times
sudo journalctl -u backend --since "2024-03-20" --until "2024-03-21"

# Show only errors
sudo journalctl -u backend -p err

# Export logs to file
sudo journalctl -u backend > backend-logs.txt
Enter fullscreen mode Exit fullscreen mode

PM2 Monitoring

Monitor Dashboard

# Real-time monitoring (press Ctrl+C to exit)
pm2 monit

# Shows:
# - CPU usage
# - Memory usage
# - Process status
# - Logs in real-time
Enter fullscreen mode Exit fullscreen mode

Check Status

# List all processes
pm2 list

# Detailed info
pm2 describe backend

# JSON output (for scripts)
pm2 jlist
Enter fullscreen mode Exit fullscreen mode

View Logs

# All logs
pm2 logs

# Specific app
pm2 logs backend

# Last 100 lines
pm2 logs backend --lines 100

# Follow logs
pm2 logs backend -f

# Error logs only
pm2 logs backend --err

# Clear all logs
pm2 flush
Enter fullscreen mode Exit fullscreen mode

System Information

# PM2 system info
pm2 info

# Show PM2 version
pm2 -v

# Show memory usage
pm2 list | grep MB
Enter fullscreen mode Exit fullscreen mode

Troubleshooting and Best Practices

Common Issues and Solutions

Issue 1: Service Won't Start

Symptoms:

sudo systemctl status backend
# Shows: failed (Result: exit-code)
Enter fullscreen mode Exit fullscreen mode

Solutions:

  1. Check the logs:
sudo journalctl -u backend -n 50
Enter fullscreen mode Exit fullscreen mode
  1. Common causes:

    • Missing .env file
    • Wrong file paths in service file
    • Port already in use
    • Missing dependencies
  2. Test manually:

cd /home/ubuntu/backend-app
node src/server.js
# See what error appears
Enter fullscreen mode Exit fullscreen mode
  1. Check permissions:
ls -la /home/ubuntu/backend-app
# Make sure ubuntu user owns the files
sudo chown -R ubuntu:ubuntu /home/ubuntu/backend-app
Enter fullscreen mode Exit fullscreen mode

Issue 2: Port Already in Use

Symptoms:

Error: listen EADDRINUSE: address already in use :::5000
Enter fullscreen mode Exit fullscreen mode

Solutions:

  1. Find what's using the port:
sudo lsof -i :5000
sudo netstat -tulpn | grep 5000
Enter fullscreen mode Exit fullscreen mode
  1. Kill the process:
sudo kill -9 <PID>
Enter fullscreen mode Exit fullscreen mode
  1. Or change your app's port in .env

Issue 3: Caddy Can't Get SSL Certificate

Symptoms:

certificate retrieval failed
Enter fullscreen mode Exit fullscreen mode

Solutions:

  1. Make sure DNS is pointing to your server:
dig yourdomain.com
nslookup yourdomain.com
Enter fullscreen mode Exit fullscreen mode
  1. Check if ports 80 and 443 are open:
sudo ufw status
# Should show 80/tcp and 443/tcp ALLOW
Enter fullscreen mode Exit fullscreen mode
  1. Check Caddy logs:
sudo journalctl -u caddy -f
Enter fullscreen mode Exit fullscreen mode
  1. Verify domain ownership:
# Make sure A record points to your EC2 IP
curl -I http://yourdomain.com
Enter fullscreen mode Exit fullscreen mode
  1. Test Caddy config:
sudo caddy validate --config /etc/caddy/Caddyfile
Enter fullscreen mode Exit fullscreen mode

Issue 4: High Memory Usage

Symptoms:

  • Server becomes slow
  • Out of memory errors
  • Applications crash randomly

Solutions:

  1. Check memory usage:
free -h
htop  # Install with: sudo apt install htop
Enter fullscreen mode Exit fullscreen mode
  1. Find memory-hungry processes:
ps aux --sort=-%mem | head -n 10
Enter fullscreen mode Exit fullscreen mode
  1. For systemd, add memory limits:
[Service]
MemoryMax=512M
MemoryHigh=400M
Enter fullscreen mode Exit fullscreen mode
  1. For PM2, configure max memory restart:
max_memory_restart: '500M'
Enter fullscreen mode Exit fullscreen mode
  1. Optimize Node.js memory:
# In your service file or PM2 config
NODE_OPTIONS="--max-old-space-size=512"
Enter fullscreen mode Exit fullscreen mode

Issue 5: Application Crashes After Deployment

Symptoms:

  • App works locally but crashes on server
  • "Module not found" errors

Solutions:

  1. Check Node.js version matches:
node -v
# Compare with your local version
Enter fullscreen mode Exit fullscreen mode
  1. Reinstall dependencies:
cd /home/ubuntu/backend-app
rm -rf node_modules package-lock.json
npm install --production
Enter fullscreen mode Exit fullscreen mode
  1. Check for missing environment variables:
# Add console.log to verify .env is loaded
console.log('Environment:', process.env.NODE_ENV);
Enter fullscreen mode Exit fullscreen mode
  1. Verify file permissions:
sudo chown -R ubuntu:ubuntu /home/ubuntu/backend-app
chmod -R 755 /home/ubuntu/backend-app
Enter fullscreen mode Exit fullscreen mode

Issue 6: Frontend Shows 502 Bad Gateway

Symptoms:

  • Caddy returns 502 error
  • "upstream connect error"

Solutions:

  1. Check if frontend is actually running:
curl localhost:3000
# Should return HTML

sudo systemctl status frontend
# Should show "active (running)"
Enter fullscreen mode Exit fullscreen mode
  1. Check frontend logs:
sudo journalctl -u frontend -n 50
Enter fullscreen mode Exit fullscreen mode
  1. Verify port in Caddyfile matches your app:
# In Caddyfile should be:
reverse_proxy localhost:3000

# In your Next.js app:
PORT=3000 npm start
Enter fullscreen mode Exit fullscreen mode
  1. Test connection manually:
telnet localhost 3000
# Should connect successfully
Enter fullscreen mode Exit fullscreen mode

Best Practices

Security Best Practices

  1. Never expose .env files:
# Make sure .env is in .gitignore
echo ".env" >> .gitignore
echo ".env.*" >> .gitignore

# Set proper permissions
chmod 600 /home/ubuntu/backend-app/.env
Enter fullscreen mode Exit fullscreen mode
  1. Use environment-specific secrets:
# Different secrets for dev/staging/production
JWT_SECRET_DEV=dev-secret-key
JWT_SECRET_PROD=super-complex-prod-key-12345
Enter fullscreen mode Exit fullscreen mode
  1. Keep dependencies updated:
# Check for vulnerabilities
npm audit

# Fix automatically (test first!)
npm audit fix

# Update dependencies
npm update
Enter fullscreen mode Exit fullscreen mode
  1. Use SSH keys only (disable password auth):
sudo nano /etc/ssh/sshd_config

# Set these values:
PasswordAuthentication no
PermitRootLogin no
PubkeyAuthentication yes

sudo systemctl restart sshd
Enter fullscreen mode Exit fullscreen mode
  1. Configure fail2ban to prevent brute force:
sudo apt install fail2ban -y
sudo systemctl enable fail2ban
sudo systemctl start fail2ban
Enter fullscreen mode Exit fullscreen mode

Performance Best Practices

  1. Use PM2 cluster mode for CPU-intensive apps:
{
  name: 'backend',
  instances: 'max',  // Uses all CPU cores
  exec_mode: 'cluster'
}
Enter fullscreen mode Exit fullscreen mode
  1. Enable Caddy compression:
yourdomain.com {
    encode gzip zstd
    reverse_proxy localhost:3000
}
Enter fullscreen mode Exit fullscreen mode
  1. Use production builds:
# Next.js
NODE_ENV=production npm run build

# React
npm run build
Enter fullscreen mode Exit fullscreen mode
  1. Implement health checks:
// In your Express app
app.get('/health', (req, res) => {
  res.status(200).json({ 
    status: 'ok', 
    timestamp: new Date().toISOString() 
  });
});
Enter fullscreen mode Exit fullscreen mode
  1. Use CDN for static assets:
  2. Images, CSS, JS should be on S3 + CloudFront
  3. Reduces server load
  4. Faster delivery worldwide

Deployment Best Practices

  1. Use Git tags for versions:
# Tag your releases
git tag -a v1.0.0 -m "Production release 1.0.0"
git push origin v1.0.0

# Deploy specific version
git checkout v1.0.0
Enter fullscreen mode Exit fullscreen mode
  1. Create deployment script:
nano /home/ubuntu/deploy-backend.sh
Enter fullscreen mode Exit fullscreen mode

Add:

#!/bin/bash
set -e  # Exit on error

echo "πŸš€ Starting deployment..."

cd /home/ubuntu/backend-app

echo "πŸ“₯ Pulling latest code..."
git pull origin main

echo "πŸ“¦ Installing dependencies..."
npm install --production

echo "πŸ§ͺ Running tests..."
npm test

echo "πŸ”„ Restarting service..."
sudo systemctl restart backend

echo "βœ… Deployment complete!"
Enter fullscreen mode Exit fullscreen mode

Make executable:

chmod +x /home/ubuntu/deploy-backend.sh
Enter fullscreen mode Exit fullscreen mode

Run:

./deploy-backend.sh
Enter fullscreen mode Exit fullscreen mode
  1. Implement zero-downtime deployment with PM2:
# Instead of restart, use reload
pm2 reload backend

# This:
# 1. Starts new instances
# 2. Waits for them to be ready
# 3. Stops old instances
# 4. Zero downtime!
Enter fullscreen mode Exit fullscreen mode
  1. Keep backup of previous version:
# Before deploying
cp -r /home/ubuntu/backend-app /home/ubuntu/backend-app.backup

# If deployment fails, rollback:
rm -rf /home/ubuntu/backend-app
mv /home/ubuntu/backend-app.backup /home/ubuntu/backend-app
sudo systemctl restart backend
Enter fullscreen mode Exit fullscreen mode
  1. Use separate staging environment:
# Different ports for staging
STAGING_PORT=5001
PRODUCTION_PORT=5000

# Different subdomains
staging.yourdomain.com β†’ localhost:5001
api.yourdomain.com β†’ localhost:5000
Enter fullscreen mode Exit fullscreen mode

Monitoring Best Practices

  1. Set up log rotation:

For systemd:

sudo nano /etc/systemd/journald.conf

# Add these settings:
SystemMaxUse=500M
SystemKeepFree=1G
MaxRetentionSec=1week
Enter fullscreen mode Exit fullscreen mode

For PM2:

{
  log_date_format: 'YYYY-MM-DD HH:mm:ss',
  merge_logs: true,
  max_size: '10M',
  retain: 7  // Keep 7 log files
}
Enter fullscreen mode Exit fullscreen mode
  1. Monitor disk space:
# Check disk usage
df -h

# Find large files
du -sh /home/ubuntu/* | sort -rh | head -n 10

# Clean up PM2 logs
pm2 flush

# Clean up system logs
sudo journalctl --vacuum-time=7d
Enter fullscreen mode Exit fullscreen mode
  1. Set up alerts (optional):
# Install monitoring tools
sudo apt install monitoring-plugins -y

# Or use cloud monitoring:
# - AWS CloudWatch
# - Datadog
# - New Relic
Enter fullscreen mode Exit fullscreen mode
  1. Regular backups:
# Backup script
#!/bin/bash
DATE=$(date +%Y%m%d_%H%M%S)
tar -czf /home/ubuntu/backups/app-$DATE.tar.gz \
  /home/ubuntu/backend-app \
  /home/ubuntu/frontend-app \
  /etc/caddy/Caddyfile

# Keep only last 7 backups
ls -t /home/ubuntu/backups/*.tar.gz | tail -n +8 | xargs rm -f
Enter fullscreen mode Exit fullscreen mode

Add to crontab:

crontab -e

# Run backup daily at 2 AM
0 2 * * * /home/ubuntu/backup.sh
Enter fullscreen mode Exit fullscreen mode

Deployment Workflow Comparison

Manual Deployment (Basic)

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ 1. Developer pushes code to GitHub     β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                   β”‚
                   ↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ 2. SSH into EC2 server                  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                   β”‚
                   ↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ 3. cd /home/ubuntu/backend-app          β”‚
β”‚    git pull origin main                 β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                   β”‚
                   ↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ 4. npm install                          β”‚
β”‚    npm run build (if needed)            β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                   β”‚
                   ↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ 5. sudo systemctl restart backend       β”‚
β”‚    OR                                   β”‚
β”‚    pm2 reload backend                   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                   β”‚
                   ↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ 6. Test the application                 β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

⏱️ Time: 5-10 minutes
πŸ‘€ Manual effort required
⚠️ Potential downtime
Enter fullscreen mode Exit fullscreen mode

Automated Deployment with GitHub Actions (Advanced)

# .github/workflows/deploy.yml
name: Deploy to EC2

on:
  push:
    branches: [ main ]

jobs:
  deploy:
    runs-on: ubuntu-latest

    steps:
    - name: Checkout code
      uses: actions/checkout@v2

    - name: Deploy to EC2
      uses: appleboy/ssh-action@master
      with:
        host: ${{ secrets.EC2_HOST }}
        username: ubuntu
        key: ${{ secrets.EC2_SSH_KEY }}
        script: |
          cd /home/ubuntu/backend-app
          git pull origin main
          npm install --production
          pm2 reload backend
Enter fullscreen mode Exit fullscreen mode
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ 1. Developer pushes to GitHub           β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                   β”‚
                   ↓ (automatic)
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ 2. GitHub Actions triggered             β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                   β”‚
                   ↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ 3. Runs tests automatically             β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                   β”‚
                   ↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ 4. SSH to EC2 and deploy                β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                   β”‚
                   ↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ 5. Zero-downtime reload with PM2        β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                   β”‚
                   ↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ 6. Send notification (Discord/Slack)    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

⏱️ Time: 2-3 minutes
πŸ‘€ Zero manual effort
βœ… No downtime
πŸ”” Automatic notifications
Enter fullscreen mode Exit fullscreen mode

System Architecture Diagrams

Complete Request Flow with All Components

                    🌍 Internet
                        β”‚
                        β”‚ HTTPS Request
                        β”‚ https://yourdomain.com/api/users
                        β”‚
                        β–Ό
        β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
        β”‚      AWS Security Group           β”‚
        β”‚   β€’ Allow 80 (HTTP)               β”‚
        β”‚   β€’ Allow 443 (HTTPS)             β”‚
        β”‚   β€’ Allow 22 (SSH - your IP only) β”‚
        β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                        β”‚
                        β–Ό
        β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
        β”‚         UFW Firewall              β”‚
        β”‚   β€’ Port 80 β†’ Allow               β”‚
        β”‚   β€’ Port 443 β†’ Allow              β”‚
        β”‚   β€’ Port 22 β†’ Allow               β”‚
        β”‚   β€’ Other ports β†’ Deny            β”‚
        β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                        β”‚
                        β–Ό
        β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
        β”‚    Caddy (Reverse Proxy)          β”‚
        β”‚    β€’ Receives on port 443         β”‚
        β”‚    β€’ Terminates SSL/TLS           β”‚
        β”‚    β€’ Checks Caddyfile rules       β”‚
        β”‚    β€’ Routes to backend/frontend   β”‚
        β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                        β”‚
        β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
        β”‚                               β”‚
        β”‚ if /api/*                     β”‚ if /*
        β–Ό                               β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   Backend    β”‚                β”‚   Frontend   β”‚
β”‚   Node.js    β”‚                β”‚   Next.js    β”‚
β”‚              β”‚                β”‚              β”‚
β”‚ Port: 5000   β”‚                β”‚ Port: 3000   β”‚
β”‚              β”‚                β”‚              β”‚
β”‚ Managed by:  β”‚                β”‚ Managed by:  β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚                β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ systemd  β”‚ β”‚                β”‚ β”‚ systemd  β”‚ β”‚
β”‚ β”‚   OR     β”‚ β”‚                β”‚ β”‚   OR     β”‚ β”‚
β”‚ β”‚   PM2    β”‚ β”‚                β”‚ β”‚   PM2    β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚                β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚              β”‚                β”‚              β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚                β”‚              β”‚
β”‚ β”‚ Process  β”‚ β”‚                β”‚              β”‚
β”‚ β”‚ Monitor  β”‚ β”‚                β”‚              β”‚
β”‚ β”‚ β€’ Auto   β”‚ β”‚                β”‚              β”‚
β”‚ β”‚ restart  β”‚ β”‚                β”‚              β”‚
β”‚ β”‚ β€’ Logs   β”‚ β”‚                β”‚              β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚                β”‚              β”‚
β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜                β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
       β”‚
       β”‚ Database queries
       β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   Database      β”‚
β”‚ β€’ MongoDB       β”‚
β”‚ β€’ PostgreSQL    β”‚
β”‚ β€’ MySQL         β”‚
β”‚ (External)      β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
Enter fullscreen mode Exit fullscreen mode

Process Lifecycle with systemd

System Boot
    β”‚
    β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ systemd (PID 1) starts                β”‚
β”‚ Reads: /etc/systemd/system/*.service  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                β”‚
    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    β”‚           β”‚           β”‚
    β–Ό           β–Ό           β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ backend β”‚ β”‚frontend β”‚ β”‚  caddy  β”‚
β”‚ service β”‚ β”‚ service β”‚ β”‚ service β”‚
β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”˜
     β”‚           β”‚           β”‚
     β”‚ Starts    β”‚ Starts    β”‚ Starts
     β”‚ Process   β”‚ Process   β”‚ Process
     β–Ό           β–Ό           β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ node    β”‚ β”‚ npm     β”‚ β”‚ caddy   β”‚
β”‚ server  β”‚ β”‚ start   β”‚ β”‚ run     β”‚
β”‚ PID:1234β”‚ β”‚ PID:1235β”‚ β”‚ PID:1236β”‚
β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”˜
     β”‚           β”‚           β”‚
     β”‚ Running   β”‚ Running   β”‚ Running
     β”‚           β”‚           β”‚
     β–Ό           β–Ό           β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚     systemd monitors all           β”‚
β”‚     β€’ Collects logs (journald)     β”‚
β”‚     β€’ Monitors health              β”‚
β”‚     β€’ Auto-restart on crash        β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
Enter fullscreen mode Exit fullscreen mode

If process crashes:

Process Crash Detected
        β”‚
        β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ systemd detects   β”‚
β”‚ process exit      β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
          β”‚
          β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Check Restart=    β”‚
β”‚ policy            β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
          β”‚
          β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Wait RestartSec   β”‚
β”‚ (default 10s)     β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
          β”‚
          β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Start new process β”‚
β”‚ (new PID)         β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
          β”‚
          β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Log to journald   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
Enter fullscreen mode Exit fullscreen mode

Process Lifecycle with PM2

PM2 Startup
    β”‚
    β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ PM2 Daemon (God Process) starts       β”‚
β”‚ Reads: ecosystem.config.js            β”‚
β”‚ PID: 789                              β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                β”‚
    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    β”‚                       β”‚
    β–Ό                       β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Backend App     β”‚   β”‚ Frontend App β”‚
β”‚ β€’ Cluster Mode  β”‚   β”‚ β€’ Fork Mode  β”‚
β”‚ β€’ 4 instances   β”‚   β”‚ β€’ 1 instance β”‚
β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜
     β”‚                       β”‚
     β”œβ”€β”€β”¬β”€β”€β”¬β”€β”€β”¬β”€β”€            β”‚
     β–Ό  β–Ό  β–Ό  β–Ό              β–Ό
    W1 W2 W3 W4             W1
    PID PID PID PID         PID
    1234 1235 1236 1237     1238

All workers report to PM2 Daemon
Enter fullscreen mode Exit fullscreen mode

Request distribution in Cluster Mode:

Incoming Requests
        β”‚
        β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ PM2 Load Balancer  β”‚
β”‚ (Round Robin)      β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
         β”‚
    β”Œβ”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”¬β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”
    β”‚    β”‚    β”‚    β”‚    β”‚
    β–Ό    β–Ό    β–Ό    β–Ό    β–Ό
   W1   W2   W3   W4   W1
  Req1 Req2 Req3 Req4 Req5

Each worker handles requests independently
If one crashes, others continue serving
Enter fullscreen mode Exit fullscreen mode

Quick Reference Commands

systemd Commands Cheat Sheet

# Service Management
sudo systemctl start <service>      # Start service
sudo systemctl stop <service>       # Stop service
sudo systemctl restart <service>    # Restart service
sudo systemctl reload <service>     # Reload config
sudo systemctl status <service>     # Check status

# Enable/Disable (auto-start on boot)
sudo systemctl enable <service>     # Enable auto-start
sudo systemctl disable <service>    # Disable auto-start
sudo systemctl is-enabled <service> # Check if enabled

# Logs
sudo journalctl -u <service>        # All logs
sudo journalctl -u <service> -f     # Follow logs
sudo journalctl -u <service> -n 50  # Last 50 lines
sudo journalctl -u <service> --since "1 hour ago"
sudo journalctl -u <service> --since "2024-03-20 10:00"

# System Management
sudo systemctl daemon-reload        # Reload service files
sudo systemctl list-units          # List all units
sudo systemctl list-units --failed # Failed services
Enter fullscreen mode Exit fullscreen mode

PM2 Commands Cheat Sheet

# Start/Stop
pm2 start app.js --name myapp      # Start app
pm2 start ecosystem.config.js      # Start from config
pm2 stop myapp                     # Stop app
pm2 restart myapp                  # Restart app
pm2 reload myapp                   # Zero-downtime reload
pm2 delete myapp                   # Remove from PM2

# Cluster Mode
pm2 start app.js -i 4              # 4 instances
pm2 start app.js -i max            # All CPU cores
pm2 scale myapp 4                  # Scale to 4 instances
pm2 scale myapp +2                 # Add 2 instances

# Information
pm2 list                           # List all apps
pm2 describe myapp                 # Detailed info
pm2 monit                         # Real-time monitor

# Logs
pm2 logs                          # All logs
pm2 logs myapp                    # App logs
pm2 logs myapp --lines 100        # Last 100 lines
pm2 flush                         # Clear logs

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

# Updates
pm2 update                        # Update PM2 daemon
pm2 reset myapp                   # Reset restart counter
Enter fullscreen mode Exit fullscreen mode

Git Deployment Commands

# Pull latest changes
cd /home/ubuntu/backend-app
git pull origin main

# Check current branch/version
git branch                        # Current branch
git log -1                        # Last commit
git status                        # Working directory status

# Deploy specific version
git fetch --tags
git checkout v1.0.0
git checkout main                 # Back to main

# Undo changes (dangerous!)
git reset --hard origin/main      # Reset to remote
git clean -fd                     # Remove untracked files
Enter fullscreen mode Exit fullscreen mode

Server Maintenance Commands

# Disk Space
df -h                             # Disk usage
du -sh /path/*                    # Directory sizes
ncdu /home/ubuntu                 # Interactive disk usage

# Memory
free -h                           # Memory usage
htop                             # Interactive process viewer
ps aux --sort=-%mem | head       # Top memory processes

# Processes
ps aux                           # All processes
ps aux | grep node               # Find node processes
kill -9 <PID>                    # Kill process
pkill -f "node"                  # Kill by name

# Network
netstat -tulpn                   # Open ports
ss -tulpn                        # Socket statistics
lsof -i :3000                    # What's on port 3000

# Logs
tail -f /var/log/syslog          # System log
tail -f /var/log/nginx/error.log # Nginx errors
sudo journalctl -f               # All system logs
Enter fullscreen mode Exit fullscreen mode

Summary

Key Takeaways

  1. Architecture: Internet β†’ DNS β†’ EC2 β†’ Caddy β†’ Backend/Frontend β†’ Database

  2. Process Managers:

    • systemd: Native Linux, minimal overhead, works with any language
    • PM2: Node.js focused, cluster mode, zero-downtime, better monitoring
  3. Deployment Flow:

    • Clone repo β†’ Install dependencies β†’ Build (if needed) β†’ Configure service β†’ Start β†’ Monitor
  4. Best Practices:

    • Always use environment variables for secrets
    • Enable auto-restart for reliability
    • Monitor logs regularly
    • Keep backups
    • Use HTTPS everywhere (Caddy handles this)
    • Implement proper error handling
  5. Troubleshooting:

    • Check logs first (journalctl or pm2 logs)
    • Verify ports are available
    • Test manually before automating
    • Keep services updated

Quick Start Checklist

  • [ ] EC2 instance running with SSH access
  • [ ] Node.js installed
  • [ ] Git repositories cloned
  • [ ] Environment files (.env) configured
  • [ ] Dependencies installed (npm install)
  • [ ] Production build created (npm run build)
  • [ ] Process manager configured (systemd or PM2)
  • [ ] Services enabled and started
  • [ ] Caddy installed and configured
  • [ ] DNS pointing to EC2 IP
  • [ ] SSL certificate obtained (automatic with Caddy)
  • [ ] Firewall configured (ports 80, 443, 22)
  • [ ] Services tested and monitored
  • [ ] Deployment script created
  • [ ] Backup strategy in place

You're now ready to deploy and manage production applications on AWS EC2! πŸš€

Top comments (0)