Example Project: ProjectX
Stack: Next.js (frontend) · Express.js (backend) · MongoDB (Atlas or self-hosted)
Deployment Tools: Nginx, PM2, Let’s Encrypt (Certbot)
Target Domains:
-
dashboard.projectname.com
→ Frontend -
api.projectname.com
→ Backend
Difficulty: Intermediate
Best For: Developers deploying small to medium full-stack apps on AWS
🧭 Meta Description
A complete step-by-step guide to deploying a Next.js + Express + MongoDB app on a single AWS EC2 instance using Nginx, PM2, and Let’s Encrypt SSL — a production-ready setup for small projects.
⚙️ Architecture Overview
We’ll host both frontend and backend on one t3.small EC2 instance running Ubuntu 22.04 LTS.
- Nginx → Reverse proxy + SSL termination
- PM2 → Node process manager
-
Next.js → Served via Nginx at
dashboard.projectname.com
-
Express API → Proxied internally on port
3000
viaapi.projectname.com
- MongoDB → Hosted on MongoDB Atlas (recommended) or locally (for dev/staging)
🔑 Pre-Deployment Requirements
Before deployment, gather:
- AWS access (EC2 + Security Groups + Elastic IP)
- Domain DNS access (to point A records)
- MongoDB URI (Atlas or local credentials)
- SSL email (for Let’s Encrypt)
- SSH key for server access
- Environment variable secrets (
JWT_SECRET
,DB_URI
, etc.)
☁️ Step-by-Step EC2 Setup
1️⃣ Provision & Update Your Server
sudo apt update && sudo apt upgrade -y
sudo apt install -y build-essential git curl nginx certbot python3-certbot-nginx ufw
2️⃣ Install Node.js & PM2
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt install -y nodejs
sudo npm install -g pm2
pm2 startup systemd --hp /home/ubuntu
3️⃣ Prepare Project Directories
sudo mkdir -p /var/www/projectx/{frontend,backend}
sudo chown -R ubuntu:ubuntu /var/www/projectx
cd /var/www/projectx
4️⃣ Clone Your Repositories
cd frontend && git clone <FRONTEND_REPO_URL> .
cd ../backend && git clone <BACKEND_REPO_URL> .
5️⃣ Install Dependencies & Build
# Frontend
cd /var/www/projectx/frontend
npm ci && npm run build
# Backend
cd /var/www/projectx/backend
npm ci
⚙️ Managing Processes with PM2
Create /var/www/projectx/ecosystem.config.js
:
module.exports = {
apps: [
{
name: "projectx-backend",
cwd: "/var/www/projectx/backend",
script: "npm",
args: "start",
env: {
NODE_ENV: "production",
PORT: 3000,
MONGODB_URI: "your_mongodb_uri",
JWT_SECRET: "your_secret",
},
},
{
name: "projectx-frontend",
cwd: "/var/www/projectx/frontend",
script: "npm",
args: "run start",
env: {
NODE_ENV: "production",
PORT: 4000,
NEXT_PUBLIC_API_BASE: "https://api.projectname.com",
},
},
],
};
Start both:
pm2 start ecosystem.config.js
pm2 save
🌐 Configure Nginx Reverse Proxy
Create two files under /etc/nginx/sites-available/
and link them.
dashboard.projectname.com
server {
listen 80;
server_name dashboard.projectname.com;
return 301 https://$host$request_uri;
}
api.projectname.com
server {
listen 80;
server_name api.projectname.com;
return 301 https://$host$request_uri;
}
After SSL setup, Certbot will inject HTTPS blocks automatically, but if you prefer manual configuration:
server {
listen 443 ssl;
server_name api.projectname.com;
ssl_certificate /etc/letsencrypt/live/api.projectname.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/api.projectname.com/privkey.pem;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
Enable and reload Nginx:
sudo ln -s /etc/nginx/sites-available/api.projectname.com /etc/nginx/sites-enabled/
sudo ln -s /etc/nginx/sites-available/dashboard.projectname.com /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
🔐 Enable HTTPS with Let’s Encrypt
sudo certbot --nginx -d dashboard.projectname.com -d api.projectname.com \
--agree-tos --redirect --no-eff-email -m admin@projectname.com
sudo certbot renew --dry-run
🧱 Security & Firewall
AWS Security Group
Port | Service | Access |
---|---|---|
22 | SSH | Admin IPs only |
80 | HTTP | 0.0.0.0/0 |
443 | HTTPS | 0.0.0.0/0 |
27017 | MongoDB | Localhost or private IPs only |
UFW on EC2
sudo ufw allow OpenSSH
sudo ufw allow 'Nginx Full'
sudo ufw enable
🗄️ MongoDB — Use Atlas for Production
Self-hosting MongoDB on the same instance is possible but not ideal.
Atlas offers backups, scaling, and security with less maintenance.
If you must self-host:
- Bind to
127.0.0.1
- Enable authentication
- Automate daily backups (
mongodump
→ S3)
🔄 Deployment Automation Script
Example /usr/local/bin/deploy_projectx.sh
:
#!/usr/bin/env bash
set -e
APP_DIR="/var/www/projectx"
cd $APP_DIR/backend
git pull origin main
npm ci
pm2 restart projectx-backend
cd $APP_DIR/frontend
git pull origin main
npm ci && npm run build
pm2 restart projectx-frontend
sudo nginx -t && sudo systemctl reload nginx
🧠 Monitoring, Backups & Maintenance
- Monitoring: CloudWatch, Datadog, or PM2 Keymetrics
-
Backups:
- Database → Atlas or
mongodump
- Config → secure Git/S3
- Database → Atlas or
-
Log Rotation:
pm2 install pm2-logrotate pm2 set pm2-logrotate:max_size 10M
⚡ Scaling & Future Improvements
Area | Recommendation |
---|---|
DB | Move to MongoDB Atlas |
Infra | Separate frontend/backend instances |
CI/CD | Use GitHub Actions or AWS CodeDeploy |
Scaling | Add ALB + Auto Scaling Group |
Security | Add WAF & rate limiting |
Secrets | Use AWS Secrets Manager |
🧾 Final Handover Checklist
✅ Elastic IP + DNS records set
✅ SSL certificates issued & tested
✅ PM2 configured to auto-start
✅ Logs & backups working
✅ Security rules locked down
✅ Credentials documented securely
✍️ Wrapping Up
Hosting your full-stack Next.js + Express app on a single EC2 instance is efficient for small projects and prototypes.
With PM2, Nginx, and Certbot, you get a production-grade deployment without Kubernetes or ECS complexity.
As your app grows, migrate MongoDB to Atlas, host the frontend on Vercel, and implement CI/CD workflows for automated deployments.
—all generic and reusable for your own stack.
Top comments (0)