AWS DevOps

Deploying NestJS on AWS EC2:
Nginx, PM2 & SSL Guide

By Ankit Kumar, Founder of Kyvronix Technologies · 22 min read

Vercel and Heroku are great for side projects, but when you need raw compute power, WebSockets, and predictable billing for a production application, nothing beats a raw AWS EC2 instance. At Kyvronix, our backend APIs—including BhuMitra—run on custom EC2 Linux instances.

This is the ultimate, no-fluff guide to taking a NestJS application from your local machine to a production AWS EC2 instance with Nginx acting as a reverse proxy, PM2 utilizing all CPU cores, and Certbot securing it with SSL.

1. Provisioning the EC2 Instance

First, spin up an Ubuntu Server 24.04 LTS instance on AWS. Make sure your Security Group allows:

SSH into your server and install Node.js (via NVM) and Nginx:

sudo apt update && sudo apt upgrade -y
sudo apt install nginx curl git -y

# Install NVM & Node
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
source ~/.bashrc
nvm install 20
  

2. Building the NestJS App

Clone your repository, install dependencies, and build the app. NestJS compiles down to a single main.js file in the dist/ folder.

git clone https://github.com/your-repo/your-api.git
cd your-api
npm install
npm run build
  

3. PM2: Clustering for Node.js

Node.js is single-threaded. If your EC2 instance has 4 cores, running node dist/main.js will only use 1 core. PM2 solves this by running multiple instances and load-balancing between them.

npm install -g pm2

# Start in cluster mode (uses all available CPUs)
pm2 start dist/main.js -i max --name "nestjs-api"

# Ensure PM2 restarts on server reboot
pm2 startup
pm2 save
  

To implement Zero-Downtime deployments later, you can use pm2 reload nestjs-api, which gracefully restarts one worker at a time.

4. Nginx Reverse Proxy

Node.js should never be exposed directly to port 80/443. Nginx is battle-tested against slowloris attacks and efficiently serves static assets. Let's configure Nginx to forward traffic to PM2 (which is running on port 3000).

sudo nano /etc/nginx/sites-available/api.yourdomain.com
  
server {
    listen 80;
    server_name api.yourdomain.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_cache_bypass $http_upgrade;
    }
}
  

Enable it and restart Nginx:

sudo ln -s /etc/nginx/sites-available/api.yourdomain.com /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl restart nginx
  

5. Securing with Let's Encrypt (SSL)

HTTP is dead. Let's secure our API with a free SSL certificate from Let's Encrypt using Certbot.

sudo apt install certbot python3-certbot-nginx -y
sudo certbot --nginx -d api.yourdomain.com
  

Certbot will automatically modify your Nginx config to listen on port 443 and setup an auto-renewal cron job. Your API is now securely live!

Conclusion

Deploying manually teaches you what PaaS providers (like Heroku) actually do behind the scenes. With PM2 handling process crashes and load balancing, Nginx handling security and TLS, and EC2 providing the compute, you have a highly scalable, production-grade architecture that costs a fraction of managed services.

— Ankit Kumar