๐ Table of Contents
- Architecture Overview
- Request Flow Explained
- systemd vs PM2
- Step by Step Deployment Guide
- Process Management Deep Dive
- Monitoring and Logs
- Troubleshooting and Best Practices
- 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 โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
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
โโโโโโโโโโโโโโโโโโโ
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
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
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 โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
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 โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
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
1.2 Update System
sudo apt update && sudo apt upgrade -y
1.3 Install Essential Tools
sudo apt install -y git curl build-essential
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
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
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
3.2 Create Environment File
nano .env
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
Security tip: Make sure .env is in .gitignore!
3.3 Install Dependencies
npm install --production
3.4 Test Backend Locally
# Test run
npm start
# You should see something like:
# Server running on port 5000
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
4.2 Create Environment File
nano .env.production
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
4.3 Install and Build
npm install
npm run build
# This creates the .next folder with optimized production build
4.4 Test Frontend Locally
npm start
# Should start on port 3000
Press Ctrl + C to stop.
Part 5: Process Management - Option A (systemd)
5.1 Create Backend Service
sudo nano /etc/systemd/system/backend.service
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
5.2 Create Frontend Service
sudo nano /etc/systemd/system/frontend.service
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
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
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
Part 6: Process Management - Option B (PM2)
6.1 Install PM2 Globally
sudo npm install -g pm2
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
6.3 Configure PM2 with ecosystem.config.js
Create a config file for better management:
cd /home/ubuntu
nano ecosystem.config.js
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
}
]
};
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)
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
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
7.2 Configure Caddyfile
sudo nano /etc/caddy/Caddyfile
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"
}
}
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"
}
}
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
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.
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
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) โ
โโโโโโโโโโโโโโโโโโโโ
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
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)
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
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
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
Check Status
# List all processes
pm2 list
# Detailed info
pm2 describe backend
# JSON output (for scripts)
pm2 jlist
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
System Information
# PM2 system info
pm2 info
# Show PM2 version
pm2 -v
# Show memory usage
pm2 list | grep MB
Troubleshooting and Best Practices
Common Issues and Solutions
Issue 1: Service Won't Start
Symptoms:
sudo systemctl status backend
# Shows: failed (Result: exit-code)
Solutions:
- Check the logs:
sudo journalctl -u backend -n 50
-
Common causes:
- Missing .env file
- Wrong file paths in service file
- Port already in use
- Missing dependencies
Test manually:
cd /home/ubuntu/backend-app
node src/server.js
# See what error appears
- Check permissions:
ls -la /home/ubuntu/backend-app
# Make sure ubuntu user owns the files
sudo chown -R ubuntu:ubuntu /home/ubuntu/backend-app
Issue 2: Port Already in Use
Symptoms:
Error: listen EADDRINUSE: address already in use :::5000
Solutions:
- Find what's using the port:
sudo lsof -i :5000
sudo netstat -tulpn | grep 5000
- Kill the process:
sudo kill -9 <PID>
- Or change your app's port in .env
Issue 3: Caddy Can't Get SSL Certificate
Symptoms:
certificate retrieval failed
Solutions:
- Make sure DNS is pointing to your server:
dig yourdomain.com
nslookup yourdomain.com
- Check if ports 80 and 443 are open:
sudo ufw status
# Should show 80/tcp and 443/tcp ALLOW
- Check Caddy logs:
sudo journalctl -u caddy -f
- Verify domain ownership:
# Make sure A record points to your EC2 IP
curl -I http://yourdomain.com
- Test Caddy config:
sudo caddy validate --config /etc/caddy/Caddyfile
Issue 4: High Memory Usage
Symptoms:
- Server becomes slow
- Out of memory errors
- Applications crash randomly
Solutions:
- Check memory usage:
free -h
htop # Install with: sudo apt install htop
- Find memory-hungry processes:
ps aux --sort=-%mem | head -n 10
- For systemd, add memory limits:
[Service]
MemoryMax=512M
MemoryHigh=400M
- For PM2, configure max memory restart:
max_memory_restart: '500M'
- Optimize Node.js memory:
# In your service file or PM2 config
NODE_OPTIONS="--max-old-space-size=512"
Issue 5: Application Crashes After Deployment
Symptoms:
- App works locally but crashes on server
- "Module not found" errors
Solutions:
- Check Node.js version matches:
node -v
# Compare with your local version
- Reinstall dependencies:
cd /home/ubuntu/backend-app
rm -rf node_modules package-lock.json
npm install --production
- Check for missing environment variables:
# Add console.log to verify .env is loaded
console.log('Environment:', process.env.NODE_ENV);
- Verify file permissions:
sudo chown -R ubuntu:ubuntu /home/ubuntu/backend-app
chmod -R 755 /home/ubuntu/backend-app
Issue 6: Frontend Shows 502 Bad Gateway
Symptoms:
- Caddy returns 502 error
- "upstream connect error"
Solutions:
- Check if frontend is actually running:
curl localhost:3000
# Should return HTML
sudo systemctl status frontend
# Should show "active (running)"
- Check frontend logs:
sudo journalctl -u frontend -n 50
- Verify port in Caddyfile matches your app:
# In Caddyfile should be:
reverse_proxy localhost:3000
# In your Next.js app:
PORT=3000 npm start
- Test connection manually:
telnet localhost 3000
# Should connect successfully
Best Practices
Security Best Practices
- 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
- Use environment-specific secrets:
# Different secrets for dev/staging/production
JWT_SECRET_DEV=dev-secret-key
JWT_SECRET_PROD=super-complex-prod-key-12345
- Keep dependencies updated:
# Check for vulnerabilities
npm audit
# Fix automatically (test first!)
npm audit fix
# Update dependencies
npm update
- 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
- Configure fail2ban to prevent brute force:
sudo apt install fail2ban -y
sudo systemctl enable fail2ban
sudo systemctl start fail2ban
Performance Best Practices
- Use PM2 cluster mode for CPU-intensive apps:
{
name: 'backend',
instances: 'max', // Uses all CPU cores
exec_mode: 'cluster'
}
- Enable Caddy compression:
yourdomain.com {
encode gzip zstd
reverse_proxy localhost:3000
}
- Use production builds:
# Next.js
NODE_ENV=production npm run build
# React
npm run build
- Implement health checks:
// In your Express app
app.get('/health', (req, res) => {
res.status(200).json({
status: 'ok',
timestamp: new Date().toISOString()
});
});
- Use CDN for static assets:
- Images, CSS, JS should be on S3 + CloudFront
- Reduces server load
- Faster delivery worldwide
Deployment Best Practices
- 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
- Create deployment script:
nano /home/ubuntu/deploy-backend.sh
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!"
Make executable:
chmod +x /home/ubuntu/deploy-backend.sh
Run:
./deploy-backend.sh
- 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!
- 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
- 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
Monitoring Best Practices
- Set up log rotation:
For systemd:
sudo nano /etc/systemd/journald.conf
# Add these settings:
SystemMaxUse=500M
SystemKeepFree=1G
MaxRetentionSec=1week
For PM2:
{
log_date_format: 'YYYY-MM-DD HH:mm:ss',
merge_logs: true,
max_size: '10M',
retain: 7 // Keep 7 log files
}
- 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
- Set up alerts (optional):
# Install monitoring tools
sudo apt install monitoring-plugins -y
# Or use cloud monitoring:
# - AWS CloudWatch
# - Datadog
# - New Relic
- 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
Add to crontab:
crontab -e
# Run backup daily at 2 AM
0 2 * * * /home/ubuntu/backup.sh
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
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
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ 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
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) โ
โโโโโโโโโโโโโโโโโโโ
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 โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
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 โ
โโโโโโโโโโโโโโโโโโโโโ
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
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
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
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
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
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
Summary
Key Takeaways
Architecture: Internet โ DNS โ EC2 โ Caddy โ Backend/Frontend โ Database
-
Process Managers:
- systemd: Native Linux, minimal overhead, works with any language
- PM2: Node.js focused, cluster mode, zero-downtime, better monitoring
-
Deployment Flow:
- Clone repo โ Install dependencies โ Build (if needed) โ Configure service โ Start โ Monitor
-
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
-
Troubleshooting:
- Check logs first (
journalctlorpm2 logs) - Verify ports are available
- Test manually before automating
- Keep services updated
- Check logs first (
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)