A complete guide to deploying a MERN (MongoDB, Express.js, React, Node.js) application on AWS EC2 with Nginx reverse proxy, Application Load Balancers, and Cloudflare DNS integration.
This project demonstrates DevOps best practices for scalability, security, and high availability.
The project deploys a Travel Memory application using the MERN stack on AWS EC2 instances.
Key highlights:
- Separate Application Load Balancers for frontend and backend services
- MongoDB Atlas for database storage
- Cloudflare DNS for domain management and SSL integration
- Ensures scalable architecture with redundancy and high availability
- MongoDB connection string
- VPC with 2 public and 2 private subnets
- AMI instance with prerequisites installed (Node.js, Nginx, Git)
- Two EC2 instances for backend and two EC2 instances for frontend
- Target Groups and Application Load Balancers for backend and frontend
- A domain name to host the website on the internet
This architecture demonstrates a scalable cloud-native MERN deployment on AWS with:
- Cloudflare for DNS and SSL termination
- Application Load Balancers (Frontend & Backend)
- Auto Scaling EC2 instances
- Private subnet isolation for backend services
- External MongoDB Atlas integration
Flow of the application:
- End user opens browser β hits
https://www.yourdomain.com - DNS routes request to Frontend ALB (HTTPS, port 443)
- ALB performs health checks and distributes traffic across frontend EC2 instances
- Frontend communicates with backend via backend DNS (
url.js) - Request routed to Backend ALB (HTTPS, port 443)
- Backend ALB performs health checks and forwards to backend EC2 instances
- Backend communicates with MongoDB Atlas using connection string in
.env - Response flows back: MongoDB β Backend β Backend ALB β Frontend β Browser
Note
Architecture diagram can be reused from draw.io resources
- Create a dedicated VPC for deployment (not mandatory).
- OS: Ubuntu
- Type:
t3.micro - Network: selected VPC
- Subnet: public subnet
- Auto-assign public IP: Enabled
- Security group: allow SSH, HTTP, HTTPS, ports 3000 & 3001
πΈ [Screenshot : EC2 instance creation]

Update and upgrade OS packages:
sudo apt update && sudo apt upgrade -yInstall and start Nginx:
sudo apt install nginx -y
sudo systemctl start nginx
sudo systemctl enable nginxConfigure reverse proxy (/etc/nginx/sites-available/default):
server {
listen 80;
server_name yourdomain.com www.yourdomain.com;
location / {
proxy_pass http://127.0.0.1:3001;
proxy_http_version 1.1;
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_connect_timeout 10s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
}Install Node.js:
curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -
sudo apt install -y nodejsClone repository:
git clone https://github.com/UnpredictablePrashant/TravelMemoryCreate .env file:
nano .env
MONGO_URI='mongodb+srv://<user>:<password>@cluster.mongodb.net/travelmemory'
PORT=3001Reload Nginx:
sudo nginx -t
sudo systemctl reload nginx- Navigate to EC2 β Actions β Image and templates β Create image
πΈ [Screenshot : AMI creation]

- Create 1 or more EC2 instances from AMI
- For now, create 1 more backend instances
cd TravelMemory/backend
sudo npm install
node index.jsVerify versions:
node -v
npm -v- Access
http://<public-ip>/β should display backend message - Access
http://<public-ip>/helloβ should return Hello World
πΈ [Screenshot: Backend verification]

- Navigate to AWS Certificate Manager (ACM)
- Request public certificate for domain(s)
- It will navigate to a page where it will have CNAME name and CNAME value
- Add CNAME records in DNS provider (Cloudflare/Namecheap)
- Wait until ACM shows status as Issued
πΈ [Screenshot: ACM certificate request & validation]

Target Group:
- Type: Instances
- Protocol: HTTP
- Port: 80
- Health check:
/hello - Register backend EC2 instances
Application Load Balancer:
- Type: Application Load Balancer
- Scheme: Internet-facing
- Listener: HTTPS (443)
- SSL Certificate: ACM-issued certificate
- Forward traffic to backend target group
πΈ [Screenshot : Backend ALB creation]
Test:
- Access
https://<backend-alb-dns>/helloβ should return Hello World
DNS Mapping:
- Add CNAME record in Cloudflare:
- Type: CNAME
- Host:
backend - Value:
<backend-alb-dns>
πΈ [Screenshot : Cloudflare DNS entry]

Backend is now accessible at:
https://backend.yourdomain.com

- Launch 1 EC2 instance for frontend
- Configuration:
- OS: Ubuntu
- AMI: Select the custom AMI with pre-installed dependencies
- Network: Select created VPC
- Subnet: Public subnet
- Auto-assign public IP: Enabled
- Security group: allow SSH, HTTP, HTTPS, ports 3000 & 3001
Clone repository:
git clone https://github.com/UnpredictablePrashant/TravelMemory- Navigate to
TravelMemory/frontend/src - Edit
url.jsto point to backend ALB DNS
export const baseUrl = process.env.REACT_APP_BACKEND_URL || "https://backend.yourdomain.com";Example - export const baseUrl = process.env.REACT_APP_BACKEND_URL || "https://backend.saurabhsuman.online";
Run commands on both frontend EC2 instances:
cd TravelMemory/frontend
sudo npm install
sudo npm run build # create optimized production build
Verify versions:
node -v
npm -v
Copy Build Files to Web Directory:
sudo mkdir -p /var/www/frontend
sudo cp -r ~/TravelMemory/frontend/build/* /var/www/frontend/
Set ownership and permissions:
sudo chown -R www-data:www-data /var/www/frontend
sudo chmod -R 755 /var/www/frontend
Configure Nginx:
Edit /etc/nginx/sites-available/default:
server {
listen 80;
server_name saurabhsuman.online www.saurabhsuman.online;
root /var/www/frontend;
index index.html;
location / {
try_files $uri /index.html;
}
}
Explanation:
root β points to the React build folder.
try_files $uri /index.html; β ensures React routing works (fallback to index.html).
server_name _; β allows Nginx to respond to ALB DNS (*.elb.amazonaws.com).
Reload nginx:
sudo nginx -t # test config for syntax errors
sudo systemctl reload nginx
Verify locally:
curl -I http://localhost
Expected output:
HTTP/1.1 200 OK
- Navigate to AWS Certificate Manager (ACM)
- Request public certificate for
www.yourdomain.com - It will navigate to a page where it will have CNAME name and CNAME value
- Add CNAME records in DNS provider (Cloudflare/Namecheap)
- Wait until ACM shows status as Issued
πΈ [Screenshot : ACM certificate for frontend]
Target Group:
- Type: Instances
- Protocol: HTTP
- Port: 80
- Health check:
/(root path) - Register frontend EC2 instances
Application Load Balancer:
- Type: Application Load Balancer
- Scheme: Internet-facing
- Listener: HTTPS (443)
- SSL Certificate: ACM-issued certificate
- Forward traffic to frontend target group
πΈ [Screenshot : Frontend ALB creation]
Test:
- Access
https://<frontend-alb-dns>β should load React app homepage
- Add CNAME record in Cloudflare:
- Type: CNAME
- Host:
www - Value:
<frontend-alb-dns>
πΈ [Screenshot: Cloudflare DNS entry for frontend]

Frontend is now accessible at:
https://www.yourdomain.com






