CodeWithSabir
HomeAIDevOpsNext.jsMobile DevelopmentWeb Development
CodeWithSabir
  • Home
  • AI
  • DevOps
  • Next.js
  • Mobile Development
  • Web Development
  • About
  • Contact
CodeWithSabir

In-depth articles, tutorials, and guides on web development, React, Next.js, AI, and modern programming practices.

Topics

  • AI
  • DevOps
  • Next.js
  • Mobile Development
  • Web Development

Company

  • About
  • Contact
  • Privacy Policy
  • Terms

© 2026 CodeWithSabir. All rights reserved.

Built with SabirSoft.com

Home/DevOps/Deploying Next.js Apps on AWS EC2: A Step-by-Step Guide
DevOps

Deploying Next.js Apps on AWS EC2: A Step-by-Step Guide

Learn how to deploy a Next.js application on AWS EC2 from scratch — covering server setup, Nginx reverse proxy, SSL certificates, PM2, and automated deployments.

Sabir KhaloufiSabir KhaloufiMarch 15, 20264 min read

Vercel is the easiest way to deploy Next.js, but not every project can use it — budget constraints, compliance requirements, need for custom server configuration, or just wanting full control. AWS EC2 gives you a real Linux server you can configure exactly how you need.

This guide takes you from a fresh EC2 instance to a production-ready Next.js deployment with HTTPS, process management, and automatic restarts.

Step 1: Launch and Configure the EC2 Instance

In the AWS Console:

  • AMI: Ubuntu 24.04 LTS
  • Instance type: t3.small for most apps (t3.micro if you're on free tier)
  • Security group: Open ports 22 (SSH), 80 (HTTP), 443 (HTTPS)
  • Key pair: Create or select one — you'll need the .pem file

After launch, get your instance's public IP from the console.

Step 2: Connect and Install Dependencies

bash
# Connect via SSH
chmod 400 your-key.pem
ssh -i your-key.pem ubuntu@YOUR_EC2_IP
 
# Update system
sudo apt update && sudo apt upgrade -y
 
# Install Node.js 20 via NodeSource
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt install -y nodejs
 
# Install PM2 globally
sudo npm install -g pm2
 
# Install Nginx
sudo apt install -y nginx
 
# Verify
node --version  # v20.x.x
pm2 --version
nginx -v

Step 3: Set Up Your Application

bash
# Create app directory
sudo mkdir -p /var/www/myapp
sudo chown ubuntu:ubuntu /var/www/myapp
 
# Clone your repository
cd /var/www/myapp
git clone https://github.com/yourusername/your-nextjs-app.git .
 
# Install dependencies
npm ci --production=false
 
# Build the Next.js app
npm run build

Create your environment file:

bash
# /var/www/myapp/.env.production
nano .env.production
env
NODE_ENV=production
DATABASE_URL=your_database_url
NEXTAUTH_SECRET=your_secret
NEXTAUTH_URL=https://yourdomain.com

Step 4: Configure PM2

PM2 keeps your Node.js process alive, restarts on crash, and starts on server reboot:

javascript
// /var/www/myapp/ecosystem.config.js
module.exports = {
  apps: [
    {
      name: 'nextjs-app',
      script: 'node_modules/.bin/next',
      args: 'start',
      cwd: '/var/www/myapp',
      instances: 'max',        // Use all CPU cores
      exec_mode: 'cluster',    // Cluster mode for multi-core
      watch: false,
      max_memory_restart: '1G',
      env_production: {
        NODE_ENV: 'production',
        PORT: 3000,
      },
      error_file: '/var/log/pm2/nextjs-error.log',
      out_file: '/var/log/pm2/nextjs-out.log',
      merge_logs: true,
      time: true,
    },
  ],
}
bash
# Create log directory
sudo mkdir -p /var/log/pm2
sudo chown ubuntu:ubuntu /var/log/pm2
 
# Start the app
pm2 start ecosystem.config.js --env production
 
# Save PM2 config so it restarts after server reboot
pm2 save
 
# Set up PM2 to start on boot
pm2 startup
# Run the command it outputs (usually: sudo env PATH=... pm2 startup ...)

Step 5: Configure Nginx as Reverse Proxy

Nginx sits in front of your Node.js app — handling SSL, gzip compression, and static assets:

nginx
# /etc/nginx/sites-available/myapp
server {
    listen 80;
    server_name yourdomain.com www.yourdomain.com;
 
    # Redirect HTTP to HTTPS
    return 301 https://$host$request_uri;
}
 
server {
    listen 443 ssl http2;
    server_name yourdomain.com www.yourdomain.com;
 
    # SSL — will be filled in by Certbot
    ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;
 
    # Security headers
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header Referrer-Policy "no-referrer-when-downgrade" always;
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
 
    # Gzip
    gzip on;
    gzip_types text/plain text/css application/json application/javascript text/xml;
    gzip_min_length 1000;
 
    # Next.js static assets — cache aggressively
    location /_next/static/ {
        alias /var/www/myapp/.next/static/;
        expires 1y;
        add_header Cache-Control "public, immutable";
    }
 
    # Public folder
    location /public/ {
        alias /var/www/myapp/public/;
        expires 1d;
    }
 
    # Proxy everything else to Next.js
    location / {
        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;
        proxy_read_timeout 60s;
    }
}
bash
# Enable the site
sudo ln -s /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/
sudo rm /etc/nginx/sites-enabled/default
 
# Test config
sudo nginx -t
 
# Reload
sudo systemctl reload nginx

Step 6: SSL with Let's Encrypt

bash
# Install Certbot
sudo apt install -y certbot python3-certbot-nginx
 
# Get certificate (temporarily opens port 80)
sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com
 
# Certbot automatically modifies your Nginx config and sets up auto-renewal
# Verify renewal will work
sudo certbot renew --dry-run

Your site is now live at https://yourdomain.com.

Step 7: Automated Deployments

Create a deployment script:

bash
# /var/www/myapp/deploy.sh
#!/bin/bash
set -e
 
echo "Starting deployment..."
cd /var/www/myapp
 
# Pull latest code
git pull origin main
 
# Install new dependencies
npm ci --production=false
 
# Build
npm run build
 
# Reload PM2 with zero downtime
pm2 reload ecosystem.config.js --env production
 
echo "Deployment complete!"
bash
chmod +x deploy.sh

For GitHub Actions automation, add a deploy job that SSH's in and runs this script — exactly like the CI/CD pipeline covered in the GitHub Actions article on this blog.

Monitoring and Logs

bash
# Real-time logs
pm2 logs nextjs-app --lines 100
 
# Process status
pm2 status
 
# CPU/memory usage
pm2 monit
 
# Nginx access logs
sudo tail -f /var/log/nginx/access.log
 
# Nginx error logs
sudo tail -f /var/log/nginx/error.log

Common Mistakes

1. Not setting up a firewall. Enable UFW:

bash
sudo ufw allow 22/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable

2. Running Node as root. Use the ubuntu user, not root.

3. Forgetting NEXTAUTH_URL in production. NextAuth needs to know the production URL — without it, OAuth callbacks fail.

4. Not configuring swap. Small instances can run out of RAM during npm run build. Add 2GB swap:

bash
sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab

5. Not caching static assets in Nginx. Next.js generates hashed filenames for JS/CSS — they can be cached permanently. The Nginx config above handles this.

Key Takeaways

  • PM2 cluster mode uses all CPU cores and handles zero-downtime reloads
  • Nginx handles SSL, gzip, and static asset caching — keep Node.js only for dynamic requests
  • Let's Encrypt SSL is free and auto-renews via Certbot
  • Add swap space on small instances to prevent OOM during builds
  • Always set up UFW firewall — close all ports except 22, 80, 443
  • Keep your deploy script simple: pull, install, build, reload
#aws#ec2#next.js#deployment#nginx#ssl
Share:
Sabir Khaloufi — author photo

Written by

Sabir Khaloufi

Full-stack developer and tech blogger sharing in-depth tutorials on React, Next.js, AI, and modern web development.

On this page

  • Step 1: Launch and Configure the EC2 Instance
  • Step 2: Connect and Install Dependencies
  • Step 3: Set Up Your Application
  • Step 4: Configure PM2
  • Step 5: Configure Nginx as Reverse Proxy
  • Step 6: SSL with Let's Encrypt
  • Step 7: Automated Deployments
  • Monitoring and Logs
  • Common Mistakes
  • Key Takeaways

Related Articles

CI/CD Pipeline with GitHub Actions and Docker: A Complete Guide
DevOps

CI/CD Pipeline with GitHub Actions and Docker: A Complete Guide

Learn how to build a production-ready CI/CD pipeline using GitHub Actions and Docker. Covers testing, building images, pushing to registries, and deploying automatically.

Sabir KhaloufiMarch 25, 20265 min read
DevOps

Docker for Developers: From Zero to Production-Ready

A practical Docker guide for developers — from understanding containers to running multi-service apps with Docker Compose and preparing images for production deployment.

Sabir KhaloufiMarch 20, 20264 min read
DevOps

Kubernetes Basics Every Developer Should Know

A practical Kubernetes introduction for developers — covering Pods, Deployments, Services, ConfigMaps, and the core concepts you need to deploy and manage containerized apps.

Sabir KhaloufiMarch 10, 20264 min read