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:
- Port 22 (SSH) - Restricted to your IP.
- Port 80 (HTTP) - Open to the world.
- Port 443 (HTTPS) - Open to the world.
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