Skip to main content

Deploy Express.js on AWS EC2 — step by step

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:

PortProtocolSourcePurpose
22TCPYour IPSSH
80TCP0.0.0.0/0HTTP
443TCP0.0.0.0/0HTTPS

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.