TL;DR
Step-by-step guide to deploying an Express.js application on AWS EC2 with PM2, nginx, SSL, and CloudWatch monitoring.
Key facts
- Topology
- Express.js on AWS EC2
TL;DR
This guide covers deploying an Express.js application on an AWS EC2 instance — from instance selection through PM2 process management, nginx reverse proxy with SSL, and basic CloudWatch monitoring.
Prerequisites
- An AWS account with EC2 access
- A domain name with DNS configured (Route 53 or external provider)
- Your Express.js application in a Git repository
- An SSH key pair registered in EC2
Step 1 — Launch an EC2 instance
Choose at minimum a t3.small (2 vCPU, 2 GB RAM). For production workloads with moderate traffic, t3.medium gives headroom for npm builds and PM2 cluster mode.
Launch with Ubuntu 24.04 AMI. Configure the security group:
| Port | Protocol | Source | Purpose |
|---|---|---|---|
| 22 | TCP | Your IP | SSH |
| 80 | TCP | 0.0.0.0/0 | HTTP |
| 443 | TCP | 0.0.0.0/0 | HTTPS |
Attach an Elastic IP so your server address survives reboots. Point your domain's A record to this IP.
Step 2 — System setup and Node.js
SSH in and prepare the instance:
ssh -i ~/.ssh/mykey.pem ubuntu@your-elastic-ip
sudo apt update && sudo apt upgrade -y
sudo apt install -y curl git build-essential
Install Node.js 22 LTS via NodeSource:
curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -
sudo apt install -y nodejs
node -v
Create a deploy user:
sudo adduser deploy
sudo usermod -aG sudo deploy
sudo mkdir -p /home/deploy/.ssh
sudo cp ~/.ssh/authorized_keys /home/deploy/.ssh/
sudo chown -R deploy:deploy /home/deploy/.ssh
Step 3 — Deploy the application
sudo su - deploy
sudo mkdir -p /var/www/expressapp && sudo chown deploy:deploy /var/www/expressapp
cd /var/www/expressapp
git clone git@github.com:yourorg/expressapp.git .
npm ci --production
cp .env.example .env
# Edit .env with production values
Step 4 — PM2 process management
sudo npm install -g pm2
Create ecosystem.config.js:
module.exports = {
apps: [{
name: 'expressapp',
script: 'app.js',
instances: 'max',
exec_mode: 'cluster',
max_memory_restart: '512M',
env_production: {
NODE_ENV: 'production',
PORT: 3000,
},
error_file: '/var/log/pm2/express-error.log',
out_file: '/var/log/pm2/express-out.log',
merge_logs: true,
log_date_format: 'YYYY-MM-DD HH:mm:ss Z',
}],
};
sudo mkdir -p /var/log/pm2
pm2 start ecosystem.config.js --env production
pm2 startup systemd
pm2 save
Step 5 — nginx with SSL via Certbot
sudo apt install -y nginx certbot python3-certbot-nginx
Create /etc/nginx/sites-available/expressapp:
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
}
}
sudo ln -s /etc/nginx/sites-available/expressapp /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
sudo certbot --nginx -d example.com
Step 6 — CloudWatch basics
Install the CloudWatch agent for system-level metrics:
sudo apt install -y amazon-cloudwatch-agent
Create a basic config at /opt/aws/amazon-cloudwatch-agent/etc/config.json to track CPU, memory, and disk. Start the agent:
sudo /opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-ctl -a fetch-config -m ec2 -s -c file:/opt/aws/amazon-cloudwatch-agent/etc/config.json
Set up CloudWatch alarms for CPU > 80% and disk > 90% to get notified via SNS before issues escalate.
Deployment script
Create a repeatable deployment script at /home/deploy/deploy.sh:
#!/bin/bash
set -euo pipefail
cd /var/www/expressapp
git pull origin main
npm ci --production
pm2 reload ecosystem.config.js --env production
sleep 3
curl -sf http://localhost:3000/health > /dev/null || { echo "Health check failed"; exit 1; }
echo "Deploy complete"
With Reflex
Reflex replaces manual SSH-based deployments with audited, repeatable workflows. It monitors PM2 processes, nginx upstream health, EC2 system metrics, and can trigger deploys with built-in rollback. Instead of configuring CloudWatch alarms manually, Reflex provides a unified dashboard across all your infrastructure. See How it works.