Complete AWS EC2 Deployment Guide for Full-Stack App with Custom Domain
Deploy your Express.js backend + Vite frontend application on AWS EC2 with a custom domain and SSL certificate
Prerequisites
Before starting, ensure you have:
- AWS Account with EC2 access
- Full-stack application (Express.js backend + Vite frontend)
- MongoDB Atlas database connection string
- Basic terminal/command line knowledge
- A domain name (optional - can be added later)
Part 1: AWS EC2 Instance Setup
Step 1: Launch EC2 Instance
- Login to AWS Console and navigate to EC2 Dashboard
- Click "Launch Instance"
- Configure the following:
- Name:
my-fullstack-app
- Application and OS Images: Ubuntu Server 24.04 LTS (HVM) SSD Volume Type
- Instance Type: t3.micro (recommended) or t2.micro (free tier eligible)
- Key Pair:
- Click "Create new key pair"
- Name:
my-app-key
(or any name you prefer) - Key pair type: RSA
- Private key file format: .pem
- Click "Create key pair" and download the .pem file
- Name:
Step 2: Configure Security Group
Network Settings - Create new security group:
- Security group name:
my-app-security-group
- Description:
Security group for full-stack application
Add the following Inbound Rules:
Type | Protocol | Port | Source | Description |
---|---|---|---|---|
SSH | TCP | 22 | Anywhere (0.0.0.0/0) | SSH access |
HTTP | TCP | 80 | Anywhere (0.0.0.0/0) | Web traffic |
HTTPS | TCP | 443 | Anywhere (0.0.0.0/0) | Secure web traffic |
Custom TCP | TCP | 3000 | Anywhere (0.0.0.0/0) | Backend API |
Step 3: Configure Storage
Keep the default settings:
- 8 GiB gp3 SSD (sufficient for most applications)
- 3000 IOPS
- Not encrypted (for simplicity)
Step 4: Launch Instance
- Review your configuration
- Click "Launch Instance"
- Wait 3-5 minutes for the instance to initialize
- Note down the Public IPv4 address when available
Part 2: Prepare Your Local Application
Step 5: Update Frontend Configuration
Create frontend/.env.production
:
VITE_API_URL=/api
Update your axios configuration (e.g., axiosClient.js
):
import axios from "axios"
const axiosClient = axios.create({
baseURL: import.meta.env.VITE_API_URL || 'http://localhost:3000',
withCredentials: true,
headers: {
'Content-Type': 'application/json'
}
});
export default axiosClient;
Build your frontend:
cd frontend
npm run build
Step 6: Push to GitHub
# Make sure dist/ folder is not in .gitignore
# Comment out or remove 'dist/' from frontend/.gitignore
git add .
git commit -m "Added production build and configuration"
git push origin main
Part 3: Connect to EC2 and Setup Environment
Step 7: Setup SSH Key Permissions
On your local machine:
# Navigate to downloads folder (where .pem file is)
cd ~/Downloads
# Set correct permissions (VERY IMPORTANT!)
chmod 400 my-app-key.pem
# Optional: Move to secure location
mv my-app-key.pem ~/.ssh/
Step 8: Connect to EC2 Instance
# Connect via SSH (replace with your actual IP)
ssh -i ~/.ssh/my-app-key.pem ubuntu@YOUR-EC2-PUBLIC-IP
# Example:
ssh -i ~/.ssh/my-app-key.pem ubuntu@65.0.99.148
First connection will ask: "Are you sure you want to continue connecting?"
- Type:
yes
and press Enter
Step 9: Update System and Install Dependencies
# Update system packages
sudo apt update && sudo apt upgrade -y
# Install Node.js (latest LTS)
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt-get install -y nodejs
# Install PM2 (Process Manager)
sudo npm install -g pm2
# Install Nginx (Web Server)
sudo apt install nginx -y
# Install Git
sudo apt install git -y
# Verify installations
node --version
npm --version
pm2 --version
nginx -v
Part 4: Deploy Your Application
Step 10: Clone Your Repository
# Clone your GitHub repository
git clone https://github.com/yourusername/your-repo-name.git
cd your-repo-name
# List contents to verify
ls -la
Step 11: Setup Backend
# Navigate to backend directory
cd backend
# Install dependencies
npm install
# Create environment file
nano .env
Add your environment variables in .env:
PORT=3000
MONGODB_URI=your_mongodb_atlas_connection_string
NODE_ENV=production
# Add any other environment variables your backend needs
Save and exit: Press Ctrl + X
, then Y
, then Enter
Step 12: Start Backend with PM2
# Test backend starts correctly
node index.js
# Press Ctrl+C to stop after testing
# Start with PM2
pm2 start index.js --name "backend"
# Configure auto-start on boot
pm2 startup
# Copy and run the command PM2 shows you
# Save PM2 configuration
pm2 save
# Check status
pm2 status
Part 5: Configure Nginx
Step 13: Create Nginx Configuration
# Create Nginx site configuration
sudo nano /etc/nginx/sites-available/my-app
Add this configuration (replace paths with your actual paths):
server {
listen 80;
server_name YOUR-EC2-PUBLIC-IP; # Replace with your actual IP
# Serve frontend static files
location / {
root /home/ubuntu/your-repo-name/frontend/dist;
index index.html;
try_files $uri $uri/ /index.html;
}
# Proxy API requests to backend
location /api/ {
proxy_pass http://localhost: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;
}
}
Step 14: Enable Site and Start Nginx
# Enable your site
sudo ln -s /etc/nginx/sites-available/my-app /etc/nginx/sites-enabled/
# Remove default site (optional)
sudo rm /etc/nginx/sites-enabled/default
# Test configuration
sudo nginx -t
# Start and enable Nginx
sudo systemctl restart nginx
sudo systemctl enable nginx
Step 15: Fix File Permissions
# Set proper permissions for Nginx to access files
sudo chmod 755 /home/ubuntu/
sudo chmod 755 /home/ubuntu/your-repo-name/
sudo chmod 755 /home/ubuntu/your-repo-name/frontend/
sudo chmod -R 755 /home/ubuntu/your-repo-name/frontend/dist/
Step 16: Configure Firewall
# Configure UFW firewall
sudo ufw allow ssh
sudo ufw allow 'Nginx Full'
sudo ufw --force enable
Part 6: Test Your Application
Step 17: Test Basic Functionality
- Open browser and visit
http://YOUR-EC2-PUBLIC-IP
- Verify frontend loads correctly
- Test API functionality (login, register, etc.)
- Check browser console for any errors
Check server status:
# Check PM2 status
pm2 status
# Check Nginx status
sudo systemctl status nginx
# View backend logs if needed
pm2 logs backend
Part 7: Add Custom Domain (Optional)
Step 18: Get a Domain Name
Options:
- Free domains: Freenom.com (.tk, .ml, .ga, .cf)
- Paid domains: Namecheap, GoDaddy, Google Domains
Step 19: Configure DNS
In your domain provider's DNS settings, add:
Type | Name | Value | TTL |
---|---|---|---|
A | @ (or blank) | YOUR-EC2-PUBLIC-IP | 300 |
A | www | YOUR-EC2-PUBLIC-IP | 300 |
Step 20: Update Nginx for Domain
# Edit Nginx configuration
sudo nano /etc/nginx/sites-available/my-app
Update the server_name line:
server_name yourdomain.com www.yourdomain.com;
Test and restart:
sudo nginx -t
sudo systemctl restart nginx
Part 8: Add SSL Certificate (HTTPS)
Step 21: Install Certbot
# Install Certbot
sudo apt install certbot python3-certbot-nginx -y
Step 22: Get SSL Certificate
# Get SSL certificate (replace with your domain)
sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com
# Follow the prompts:
# - Enter email address
# - Agree to terms
# - Choose whether to share email with EFF
# - Choose to redirect HTTP to HTTPS (recommended: option 2)
Step 23: Test Auto-Renewal
# Test certificate auto-renewal
sudo certbot renew --dry-run
Part 9: Final Testing and Verification
Step 24: Complete Testing
- Visit your domain:
https://yourdomain.com
- Verify SSL certificate: Look for lock icon 🔒
- Test all functionality: Registration, login, API calls
- Check HTTPS redirect: HTTP should redirect to HTTPS
- Test www subdomain:
https://www.yourdomain.com
Maintenance and Management
Useful Commands for Ongoing Management
Backend Management:
pm2 status # Check backend status
pm2 restart backend # Restart backend
pm2 logs backend # View backend logs
pm2 stop backend # Stop backend
pm2 delete backend # Remove from PM2
Nginx Management:
sudo systemctl status nginx # Check Nginx status
sudo systemctl restart nginx # Restart Nginx
sudo nginx -t # Test configuration
sudo tail -f /var/log/nginx/error.log # View error logs
SSL Certificate Management:
sudo certbot certificates # List certificates
sudo certbot renew # Manually renew certificates
System Monitoring:
htop # System resource monitor
df -h # Disk usage
free -h # Memory usage
pm2 monit # PM2 monitoring interface
Updating Your Application
When you make changes to your code:
# On your local machine:
git add .
git commit -m "Updated application"
git push origin main
# On EC2 server:
cd /home/ubuntu/your-repo-name
git pull origin main
# If backend changed:
cd backend
npm install # if package.json changed
pm2 restart backend
# If frontend changed:
cd frontend
npm install # if package.json changed
npm run build
Troubleshooting Common Issues
Issue 1: SSH Connection Timeout
Solution: Check security group has SSH rule allowing your IP
Issue 2: 500 Internal Server Error
Solutions:
- Check file permissions:
sudo chmod -R 755 /home/ubuntu/your-repo-name/frontend/dist/
- Check Nginx error logs:
sudo tail -f /var/log/nginx/error.log
- Verify dist folder exists:
ls -la /home/ubuntu/your-repo-name/frontend/dist/
Issue 3: Backend API Not Working
Solutions:
- Check PM2 status:
pm2 status
- Check backend logs:
pm2 logs backend
- Verify environment variables in
.env
file - Check MongoDB Atlas IP whitelist
Issue 4: Domain Not Resolving
Solutions:
- Wait 5-30 minutes for DNS propagation
- Check DNS settings at your domain provider
- Use whatsmydns.net to check propagation status
Issue 5: SSL Certificate Issues
Solutions:
- Ensure domain points to correct IP
- Check certificate status:
sudo certbot certificates
- Renew if expired:
sudo certbot renew
Security Best Practices
- Keep system updated:
sudo apt update && sudo apt upgrade
- Use strong passwords and SSH keys only
- Configure firewall properly with UFW
- Regular backups of your application and database
- Monitor logs regularly for suspicious activity
- Use environment variables for sensitive data
- Keep dependencies updated: Regular
npm audit
and updates
Cost Optimization Tips
- Use t3.micro for development and small applications
- Stop instances when not needed (development environments)
- Monitor usage with AWS CloudWatch
- Use reserved instances for production workloads
- Set up billing alerts to avoid unexpected charges
Conclusion
You now have a fully deployed, production-ready full-stack application with:
✅ Ubuntu 24.04 LTS server on AWS EC2
✅ Express.js backend managed by PM2
✅ Vite React frontend served by Nginx
✅ Custom domain name with DNS configuration
✅ Free SSL certificate for HTTPS
✅ Proper security configuration
✅ Automated process management
✅ Scalable architecture
Your application is now accessible worldwide and follows industry best practices for deployment and security.
Total Setup Time: 1-2 hours
Monthly Cost: ~$8-15 (depending on instance type and usage)
Skill Level: Beginner to Intermediate
This guide covers everything needed for a complete production deployment. Bookmark this page for future reference and share it with other developers!
Prerequisites
- AWS Account with EC2 access
- Your project folder with backend (Express.js) and frontend (Vite) separated
- MongoDB Atlas connection string
- Judge0 RapidAPI key
- Basic knowledge of terminal/command line
Phase 1: AWS EC2 Setup
Step 1: Launch EC2 Instance
- Login to AWS Console and navigate to EC2 Dashboard
- Click "Launch Instance"
- Configure Instance:
- Name:
my-fullstack-app
- AMI: Ubuntu Server 22.04 LTS (Free tier eligible)
- Instance Type: t2.micro (free tier) or t3.small for better performance
- Key Pair: Create new key pair or use existing one
- Download the
.pem
file and keep it secure
- Download the
- Security Group: Create new with these rules:
- SSH (22) - Your IP
- HTTP (80) - Anywhere (0.0.0.0/0)
- HTTPS (443) - Anywhere (0.0.0.0/0)
- Custom TCP (3000) - Anywhere (for backend API)
- Custom TCP (5173) - Anywhere (for Vite dev server, optional)
- Name:
- Launch Instance
Step 2: Connect to EC2 Instance
# Make key file secure (on your local machine)
chmod 400 your-key-file.pem
# Connect to instance
ssh -i "your-key-file.pem" ubuntu@your-ec2-public-ip
Phase 2: Server Environment Setup
Step 3: Update System & Install Dependencies
# Update system packages
sudo apt update && sudo apt upgrade -y
# Install Node.js (using NodeSource repository)
curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash -
sudo apt-get install -y nodejs
# Install PM2 (Process Manager)
sudo npm install -g pm2
# Install Nginx (Web Server)
sudo apt install nginx -y
# Install Git
sudo apt install git -y
# Verify installations
node --version
npm --version
pm2 --version
nginx -v
Step 4: Clone Your Project
# Navigate to home directory
cd ~
# Clone your repository (replace with your repo URL)
git clone https://github.com/yourusername/your-repo.git
cd your-repo
# Or if uploading files directly, create project directory
mkdir my-app && cd my-app
# Then upload your files using SCP or SFTP
Phase 3: Backend Deployment
Step 5: Setup Backend
# Navigate to backend directory
cd backend # or wherever your backend code is
# Install dependencies
npm install
# Create environment file
nano .env
Add your environment variables to .env:
PORT=3000
MONGODB_URI=your_mongodb_atlas_connection_string
JUDGE0_RAPIDAPI_KEY=your_judge0_api_key
JUDGE0_RAPIDAPI_HOST=judge0-ce.p.rapidapi.com
NODE_ENV=production
# Add any other environment variables your backend needs
Step 6: Test Backend Locally
# Test if backend starts correctly
npm start
# or
node server.js # replace with your main file
# Press Ctrl+C to stop after testing
Step 7: Setup PM2 for Backend
# Start backend with PM2
pm2 start server.js --name "backend" # replace server.js with your main file
# Configure PM2 to start on system boot
pm2 startup
pm2 save
# Check status
pm2 status
pm2 logs backend # to see logs
Phase 4: Frontend Deployment
Step 8: Setup Frontend
# Navigate to frontend directory
cd ../frontend # or wherever your frontend code is
# Install dependencies
npm install
# Create production environment file
nano .env.production
Add your production environment variables:
VITE_API_URL=http://your-ec2-public-ip:3000
VITE_JUDGE0_RAPIDAPI_KEY=your_judge0_api_key
# Add other Vite environment variables
Step 9: Build Frontend
# Build production version
npm run build
# This creates a 'dist' folder with optimized files
ls dist/ # verify build was created
Phase 5: Nginx Configuration
Step 10: Configure Nginx
# Create Nginx configuration for your app
sudo nano /etc/nginx/sites-available/my-app
Add this configuration:
server {
listen 80;
server_name your-ec2-public-ip; # Replace with your actual IP or domain
# Serve frontend static files
location / {
root /home/ubuntu/your-repo/frontend/dist; # Update path to your dist folder
index index.html;
try_files $uri $uri/ /index.html;
}
# Proxy API requests to backend
location /api/ {
proxy_pass http://localhost: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;
}
}
Step 11: Enable Nginx Configuration
# Enable the site
sudo ln -s /etc/nginx/sites-available/my-app /etc/nginx/sites-enabled/
# Remove default site (optional)
sudo rm /etc/nginx/sites-enabled/default
# Test Nginx configuration
sudo nginx -t
# Restart Nginx
sudo systemctl restart nginx
sudo systemctl enable nginx
Phase 6: Final Configuration & Testing
Step 12: Update Frontend API Calls
Make sure your frontend is making API calls to /api/
instead of http://localhost:3000/
. Update your frontend code:
// Instead of: http://localhost:3000/users
// Use: /api/users
const API_BASE_URL = '/api';
Then rebuild:
cd frontend
npm run build
Step 13: Firewall Configuration
# Configure UFW firewall
sudo ufw allow ssh
sudo ufw allow 'Nginx Full'
sudo ufw allow 3000 # for direct backend access if needed
sudo ufw --force enable
Step 14: Test Your Application
- Open browser and go to
http://your-ec2-public-ip
- Test frontend loads correctly
- Test API calls work through the frontend
- Check PM2 status:
pm2 status
- Check Nginx status:
sudo systemctl status nginx
Phase 7: Domain Setup (Optional)
Step 15: Custom Domain Configuration
If you have a domain name:
- Point your domain to EC2 public IP in DNS settings
- Update Nginx configuration:
Changesudo nano /etc/nginx/sites-available/my-app
server_name
to your domain:server_name yourdomain.com www.yourdomain.com;
- Restart Nginx:
sudo systemctl restart nginx
Phase 8: SSL Certificate (Recommended)
Step 16: Install SSL with Let's Encrypt
# Install Certbot
sudo apt install certbot python3-certbot-nginx -y
# Get SSL certificate
sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com
# Test auto-renewal
sudo certbot renew --dry-run
Maintenance Commands
Useful Commands for Management
# Backend management
pm2 restart backend
pm2 stop backend
pm2 logs backend
pm2 delete backend
# Nginx management
sudo systemctl restart nginx
sudo systemctl status nginx
sudo nginx -t
# System monitoring
htop # install with: sudo apt install htop
df -h # disk usage
free -h # memory usage
# Update application
cd ~/your-repo
git pull origin main
cd backend && npm install
cd ../frontend && npm install && npm run build
pm2 restart backend
Troubleshooting
Common Issues & Solutions
-
Backend not starting:
- Check PM2 logs:
pm2 logs backend
- Verify environment variables in
.env
- Check MongoDB Atlas IP whitelist
- Check PM2 logs:
-
Frontend not loading:
- Verify Nginx configuration:
sudo nginx -t
- Check file permissions:
ls -la frontend/dist/
- Review Nginx error logs:
sudo tail -f /var/log/nginx/error.log
- Verify Nginx configuration:
-
API calls failing:
- Check if backend is running:
pm2 status
- Verify Nginx proxy configuration
- Check security group rules in AWS
- Check if backend is running:
-
502 Bad Gateway:
- Backend server is down or not responding
- Check PM2 status and restart if needed
File Structure Reference
~/your-repo/
├── backend/
│ ├── server.js
│ ├── package.json
│ ├── .env
│ └── ... (other backend files)
└── frontend/
├── dist/ (built files)
├── src/ (source files)
├── package.json
└── .env.production
Security Best Practices
- Keep system updated:
sudo apt update && sudo apt upgrade
- Use strong passwords and SSH keys only
- Configure firewall properly with UFW
- Regular backups of your application and database
- Monitor logs regularly
- Use environment variables for sensitive data
- Enable fail2ban for SSH protection:
sudo apt install fail2ban
Your application should now be successfully deployed and accessible via your EC2 instance IP address or domain name!
Written by
Suraj Kumar Jha
At
Wed Jul 25 2029