DEV Community

Cover image for Complete AWS EC2 Deployment Guide for Fullstack Apps
Saket Singh
Saket Singh

Posted on

Complete AWS EC2 Deployment Guide for Fullstack Apps

A comprehensive guide to deploying fullstack applications on AWS EC2. Deploy your Express.js backend + Vite frontend application with a custom domain and SSL certificate.

Table of Contents

  • Prerequisites
  • Part 1: AWS EC2 Instance Setup
  • Part 2: Prepare Your Local Application
  • Part 3: Connect to EC2 and Setup Environment
  • Part 4: Deploy Your Application
  • Part 5: Configure Nginx
  • Part 6: Test Your Application
  • Part 7: Add Custom Domain (Optional)
  • Part 8: Add SSL Certificate (HTTPS)
  • Part 9: Final Testing and Verification
  • Maintenance and Management
  • Troubleshooting Common Issues
  • Security Best Practices
  • Cost Optimization Tips

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:

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

  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
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

Build your frontend:

cd frontend
npm run build
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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/
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Step 11: Setup Backend

# Navigate to backend directory
cd backend

# Install dependencies
npm install

# Create environment file
nano .env
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Part 5: Configure Nginx

Step 13: Create Nginx Configuration

# Create Nginx site configuration
sudo nano /etc/nginx/sites-available/my-app
Enter fullscreen mode Exit fullscreen mode

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;
    }
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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/
Enter fullscreen mode Exit fullscreen mode

Step 16: Configure Firewall

# Configure UFW firewall
sudo ufw allow ssh
sudo ufw allow 'Nginx Full'
sudo ufw --force enable
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Update the server_name line:

server_name yourdomain.com www.yourdomain.com;
Enter fullscreen mode Exit fullscreen mode

Test and restart:

sudo nginx -t
sudo systemctl restart nginx
Enter fullscreen mode Exit fullscreen mode

Part 8: Add SSL Certificate (HTTPS)

Step 21: Install Certbot

# Install Certbot
sudo apt install certbot python3-certbot-nginx -y
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

Step 23: Test Auto-Renewal

# Test certificate auto-renewal
sudo certbot renew --dry-run
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

SSL Certificate Management:

sudo certbot certificates          # List certificates
sudo certbot renew                # Manually renew certificates
Enter fullscreen mode Exit fullscreen mode

System Monitoring:

htop                              # System resource monitor
df -h                            # Disk usage
free -h                          # Memory usage
pm2 monit                        # PM2 monitoring interface
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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: Approximately $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!

Top comments (1)

Collapse
 
lamri_abdellahramdane_15 profile image
Lamri Abdellah Ramdane

Great guide — super comprehensive! Deploying a full-stack app on EC2 (Express + Vite + Nginx + SSL) is no small feat. I also use ServBay (servbay.com) to spin up clean dev environments locally — it means when I push to EC2, things tend to just work.