Complete AWS EC2 Deployment Guide For Fullstack Apps

A comprehensive guide to deploying fullstack applications on AWS EC2.

Back

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

  1. Login to AWS Console and navigate to EC2 Dashboard
  2. Click "Launch Instance"
  3. 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

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:

TypeProtocolPortSourceDescription
SSHTCP22Anywhere (0.0.0.0/0)SSH access
HTTPTCP80Anywhere (0.0.0.0/0)Web traffic
HTTPSTCP443Anywhere (0.0.0.0/0)Secure web traffic
Custom TCPTCP3000Anywhere (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

  1. Review your configuration
  2. Click "Launch Instance"
  3. Wait 3-5 minutes for the instance to initialize
  4. 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

  1. Open browser and visit http://YOUR-EC2-PUBLIC-IP
  2. Verify frontend loads correctly
  3. Test API functionality (login, register, etc.)
  4. 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:

TypeNameValueTTL
A@ (or blank)YOUR-EC2-PUBLIC-IP300
AwwwYOUR-EC2-PUBLIC-IP300

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

  1. Visit your domain: https://yourdomain.com
  2. Verify SSL certificate: Look for lock icon 🔒
  3. Test all functionality: Registration, login, API calls
  4. Check HTTPS redirect: HTTP should redirect to HTTPS
  5. 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

  1. Keep system updated: sudo apt update && sudo apt upgrade
  2. Use strong passwords and SSH keys only
  3. Configure firewall properly with UFW
  4. Regular backups of your application and database
  5. Monitor logs regularly for suspicious activity
  6. Use environment variables for sensitive data
  7. Keep dependencies updated: Regular npm audit and updates

Cost Optimization Tips

  1. Use t3.micro for development and small applications
  2. Stop instances when not needed (development environments)
  3. Monitor usage with AWS CloudWatch
  4. Use reserved instances for production workloads
  5. 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

  1. Login to AWS Console and navigate to EC2 Dashboard
  2. Click "Launch Instance"
  3. 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
    • 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)
  4. 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

  1. Open browser and go to http://your-ec2-public-ip
  2. Test frontend loads correctly
  3. Test API calls work through the frontend
  4. Check PM2 status: pm2 status
  5. Check Nginx status: sudo systemctl status nginx

Phase 7: Domain Setup (Optional)

Step 15: Custom Domain Configuration

If you have a domain name:

  1. Point your domain to EC2 public IP in DNS settings
  2. Update Nginx configuration:
    sudo nano /etc/nginx/sites-available/my-app
    Change server_name to your domain:
    server_name yourdomain.com www.yourdomain.com;
  3. Restart Nginx: sudo systemctl restart nginx

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

  1. Backend not starting:

    • Check PM2 logs: pm2 logs backend
    • Verify environment variables in .env
    • Check MongoDB Atlas IP whitelist
  2. 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
  3. API calls failing:

    • Check if backend is running: pm2 status
    • Verify Nginx proxy configuration
    • Check security group rules in AWS
  4. 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

  1. Keep system updated: sudo apt update && sudo apt upgrade
  2. Use strong passwords and SSH keys only
  3. Configure firewall properly with UFW
  4. Regular backups of your application and database
  5. Monitor logs regularly
  6. Use environment variables for sensitive data
  7. 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