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
- 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
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: 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)
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.