In this article, we'll walk through deploying Next.js and Node.js applications on AWS Lightsail using Nginx, PM2, GitHub Actions, and Cloudflare.
Prerequisites
Before starting, ensure you have:
- AWS account with Lightsail access
- GitHub account for repository hosting
- Cloudflare account for DNS management
- Domain name pointed to Cloudflare nameservers
- Basic knowledge of Linux commands and Git
- Node.js applications ready for deployment
Architecture Overview
Our deployment architecture includes:
- AWS Lightsail: Virtual private server hosting the applications
- Nginx: Reverse proxy routing traffic to applications
- PM2: Process manager keeping Node.js apps running
- GitHub Actions: CI/CD pipeline for automated deployments
- Cloudflare: DNS management and SSL/TLS termination
Performance Improvement
This architecture provides several performance benefits:
Application-Level Performance:
Nginx serves as an efficient reverse proxy, handling SSL termination and static file serving, which reduces load on the Node.js applications. PM2 enables zero-downtime deployments through its cluster mode and automatic restarts, ensuring applications remain available during updates.
Caching and CDN:
Cloudflare's global CDN caches static assets at edge locations worldwide, significantly reducing latency for users. Nginx can also implement additional caching layers for API responses and dynamic content, further improving response times.
Resource Optimization:
PM2's cluster mode allows Node.js applications to utilize all available CPU cores, maximizing server resource utilization. Lightsail provides predictable performance with fixed resource allocations, making capacity planning straightforward.
Implementation Steps
Step 1: Set Up AWS Lightsail Instance
Create a new Lightsail instance:
# Use AWS Console or CLI
aws lightsail create-instances \
--instance-names my-app-server \
--availability-zone us-east-1a \
--blueprint-id ubuntu_22_04 \
--bundle-id nano_2_0
Console Steps:
- Log in to AWS Lightsail console
- Click "Create instance"
- Select Linux/Unix platform
- Choose Ubuntu 22.04 LTS blueprint
- Select an instance plan (recommend at least $5/month for production)
- Name your instance (e.g., "my-app-server")
- Click "Create instance"
Once the instance is running, note its static IP address. Configure the Lightsail firewall:
Firewall Rules:
- SSH (Port 22) - Your IP only
- HTTP (Port 80) - All traffic
- HTTPS (Port 443) - All traffic
Step 2: Connect and Set Up the Server
Connect to your Lightsail instance via SSH:
# Download the SSH key from Lightsail console
chmod 400 lightsail-key.pem
ssh -i lightsail-key.pem ubuntu@YOUR_STATIC_IP
Update the system and install required packages:
# Update package list
sudo apt update && sudo apt upgrade -y
# Install Nginx and Git
sudo apt install -y nginx git
# Install Node.js 24.x (LTS)
curl -fsSL https://deb.nodesource.com/setup_24.x | sudo -E bash -
sudo apt install -y nodejs
# Verify installation
node --version
npm --version
# Install PM2 globally
sudo npm install -g pm2
# Verify PM2 installation
pm2 --version
Step 3: Configure Nginx as Reverse Proxy
Create Nginx configuration for your applications:
# Create configuration file
sudo nano /etc/nginx/sites-available/myapp
Add the following configuration:
server
{
listen 80;
server_name yourdomain.com;
location /api
{
proxy_pass http://localhost:4000; # backend port
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
# CORS headers (if needed)
add_header Access-Control-Allow-Origin *;
# Rate limiting for API endpoints
location /api {
limit_req zone=api_limit burst=20 nodelay;
proxy_pass http://localhost:4000;
proxy_set_header Host $host;
}
}
location /
{
proxy_pass http://localhost:3000; # frontend port
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;
# Cache static assets
location /_next/static {
proxy_pass http://localhost:3000;
proxy_cache_valid 60m;
add_header Cache-Control "public, max-age=3600, immutable";
}
}
}
# Rate limiting configuration
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
Enable the configuration:
# Create symbolic link
sudo ln -s /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/
# Remove default configuration
sudo rm /etc/nginx/sites-enabled/default
# Test configuration
sudo nginx -t
# Reload Nginx
sudo systemctl reload nginx
# Enable Nginx to start on boot
sudo systemctl enable nginx
Step 4: Set Up Application Directories
Create directories for your applications:
# Create application directories
sudo mkdir -p /var/www/my-app
# Set ownership to ubuntu user
sudo chown -R ubuntu:ubuntu /var/www/my-app
# Create environment files directory
mkdir -p /home/ubuntu/.env-files
Step 5: Configure PM2 for Process Management
Create PM2 ecosystem configuration file:
# Create ecosystem file
nano /var/www/ecosystem.config.js
Add the following PM2 configuration:
module.exports = {
apps: [
{
name: "frontend",
cwd: "/var/www/my-app/frontend",
script: "npm",
args: "start",
env: {
NODE_ENV: "production",
PORT: 3000,
},
instances: 2,
exec_mode: "cluster",
autorestart: true,
watch: false,
max_memory_restart: "500M",
error_file: "/var/www/logs/nextjs-error.log",
out_file: "/var/www/logs/nextjs-out.log",
merge_logs: true,
time: true,
},
{
name: "api",
cwd: "/var/www/my-app/api",
script: "dist/index.js", // or 'src/index.js' if not using TypeScript
env: {
NODE_ENV: "production",
PORT: 4000,
},
instances: 2,
exec_mode: "cluster",
autorestart: true,
watch: false,
max_memory_restart: "500M",
error_file: "/var/www/logs/api-error.log",
out_file: "/var/www/logs/api-out.log",
merge_logs: true,
time: true,
},
],
};
Create logs directory:
sudo mkdir -p /var/www/logs
sudo chown -R ubuntu:ubuntu /var/www/logs
Step 6: Configure Cloudflare DNS and SSL
DNS Configuration:
- Log in to your Cloudflare dashboard
- Select your domain
- Go to DNS settings
- Add A records:
- Name:
nextapp, Content:YOUR_LIGHTSAIL_STATIC_IP, Proxy: Enabled (Orange cloud) - Name:
api, Content:YOUR_LIGHTSAIL_STATIC_IP, Proxy: Enabled (Orange cloud)
- Name:
SSL/TLS Configuration:
- Go to SSL/TLS settings in Cloudflare
- Set SSL/TLS encryption mode to "Flexible" (or "Full" if you configure SSL on Lightsail)
- Enable "Always Use HTTPS"
- Enable "Automatic HTTPS Rewrites"
- For production, consider "Full (Strict)" mode with origin certificates
Optional: Generate Cloudflare Origin Certificate (for Full Strict mode):
- In Cloudflare, go to SSL/TLS → Origin Server
- Click "Create Certificate"
- Keep default settings (RSA, 15-year validity)
- Save the certificate and private key
Install on Lightsail:
# Save certificate
sudo nano /etc/ssl/certs/cloudflare-origin.pem
# Paste certificate content
# Save private key
sudo nano /etc/ssl/private/cloudflare-origin.key
# Paste private key content
# Set permissions
sudo chmod 644 /etc/ssl/certs/cloudflare-origin.pem
sudo chmod 600 /etc/ssl/private/cloudflare-origin.key
Update Nginx configuration to use SSL:
server {
listen 443 ssl http2;
server_name yourdomain.com;
ssl_certificate /etc/ssl/certs/cloudflare-origin.pem;
ssl_certificate_key /etc/ssl/private/cloudflare-origin.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
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;
}
}
# Redirect HTTP to HTTPS
server {
listen 80;
server_name yourdomain.com;
return 301 https://$server_name$request_uri;
}
Step 7: Set Up GitHub Actions SSH Access
Generate SSH key for GitHub Actions:
# On your Lightsail instance, create deployment user SSH key
ssh-keygen -t ed25519 -C "myapp-github-actions" -f ~/.ssh/myapp-github-actions -N ""
# Add public key to authorized_keys
cat ~/.ssh/myapp-github-actions.pub >> ~/.ssh/authorized_keys
# Display private key (copy this for GitHub Secrets)
cat ~/.ssh/myapp-github-actions
Add Secrets to GitHub Repository:
- Go to your GitHub repository
- Navigate to Settings → Secrets and variables → Actions
- Add the following secrets:
-
LIGHTSAIL_HOST: Your Lightsail static IP -
LIGHTSAIL_USERNAME:ubuntu -
LIGHTSAIL_SSH_KEY: Contents of the private key from above -
LIGHTSAIL_PORT:22
-
Step 8: Create GitHub Actions Workflow for monorepo Application
Create .github/workflows/deploy-to-lightsail.yml:
name: Deploy To Lightsail
on:
push:
branches:
- main
jobs:
build-deploy:
name: Build & Deploy
runs-on: ubuntu-latest
continue-on-error: false
outputs:
node_modules_cache_key: ${{ steps.cache.outputs.cache-hit }}
steps:
- name: Checkout code
uses: actions/checkout@v5
- name: Use Node.js 24
uses: actions/setup-node@v4
with:
node-version: 24
- name: Cache node_modules
id: cache
uses: actions/cache@v4
with:
path: |
node_modules
~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('package-lock.json') }}
# ---------------- Backend Build ----------------
- name: Install backend dependencies
working-directory: api
run: npm ci
- name: Build backend
working-directory: api
run: npm run build
# ---------------- Frontend Build ----------------
- name: Install frontend dependencies
working-directory: frontend
run: npm ci
- name: Build frontend
working-directory: frontend
run: npm run build
# ---------------- Package Artifacts ----------------
- name: Prepare deployment package
run: |
mkdir -p deploy/api deploy/frontend
cp -r api/dist deploy/api/
cp api/package*.json deploy/api/
cp -r frontend/.next frontend/public frontend/package*.json deploy/frontend/
cp ecosystem.config.js deploy/
# ---------------- Deploy to Server ----------------
- name: Deploy via rsync
uses: burnett01/rsync-deployments@7.0.1
with:
switches: -avzr --delete
path: deploy/
remote_path: /var/www/my-app
remote_host: ${{ secrets.SERVER_IP }}
remote_user: ${{ secrets.SERVER_USER }}
remote_key: ${{ secrets.SSH_PRIVATE_KEY }}
# ---------------- Install & Restart on Server ----------------
- name: SSH and restart apps
uses: appleboy/ssh-action@v1.2.2
with:
host: ${{ secrets.SERVER_IP }}
username: ${{ secrets.SERVER_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
script: |
cp ~/.env-files/.env.api /var/www/my-app/api/.env
cp ~/.env-files/.env.frontend /var/www/my-app/frontend/.env.production
cd /var/www/my-app/api
npm ci
cd /var/www/my-app/frontend
npm ci --omit=dev
cd /var/www/my-app
pm2 startOrRestart ecosystem.config.js
Step 9: Test GitHub Actions Deployment
Push changes to your main branch to trigger the workflow:
git add .
git commit -m "Set up GitHub Actions deployment"
git push origin main
Monitor the Actions tab in your GitHub repository to ensure the deployment completes successfully.
Step 10: Initial Manual Deployment
Before using GitHub Actions, manually deploy your applications:
# Clone your repository
cd /var/www/my-app
git clone https://github.com/yourusername/my-app.git .
# Build Next.js app
cd /var/www/my-app/frontend
npm install
npm run build
# Build Node.js API
cd /var/www/my-app/api
npm install
npm run build # If using TypeScript
# Start applications with PM2
pm2 start /var/www/my-app/ecosystem.config.js
# Save PM2 configuration
pm2 save
# Set PM2 to start on system boot
pm2 startup
# Follow the instructions provided by the command
Step 11: Configure Environment Variables
Store environment variables securely:
# Create .env file for Next.js app
nano /var/www/my-app/frontend/.env.production
# Add your variables
NEXT_PUBLIC_API_URL=https://yourdomain.com/api
DATABASE_URL=your_database_url
API_SECRET_KEY=your_secret_key
# Create .env file for Node.js API
nano /var/www/my-app/api/.env
# Add your variables
PORT=4000
DATABASE_URL=your_database_url
JWT_SECRET=your_jwt_secret
NODE_ENV=production
# Secure the files
chmod 600 /var/www/my-app/frontend/.env.production
chmod 600 /var/www/my-app/api/.env
Add environment variables to GitHub Secrets for use in workflows if needed.
Verification
After deployment, verify everything is working:
# Check PM2 status
pm2 status
# View application logs
pm2 logs nextjs-app --lines 50
pm2 logs nodejs-api --lines 50
# Check Nginx status
sudo systemctl status nginx
# Test local endpoints
curl http://localhost:3000
curl http://localhost:4000
# Test public endpoints
curl https://yourdomain.com
curl https://yourdomain.com/api
Security Enhancement
SSL/TLS Encryption:
Cloudflare provides free SSL certificates with automatic renewal, ensuring all traffic between users and your applications is encrypted. Cloudflare's Full (Strict) SSL mode encrypts traffic end-to-end, from users to Cloudflare to your Lightsail instance.
DDoS Protection:
Cloudflare's built-in DDoS protection shields your applications from attacks at the edge, preventing malicious traffic from reaching your Lightsail instance. This protection is included in the free tier and automatically scales with attack volume.
Firewall and Access Control:
Lightsail's firewall allows you to restrict access to specific ports, ensuring only necessary services are exposed. Cloudflare's WAF (Web Application Firewall) can be configured to block common attack patterns and malicious requests before they reach your server.
Secure Deployments:
GitHub Actions with OIDC or SSH key authentication eliminates the need for long-lived credentials in your CI/CD pipeline. PM2 runs applications with limited user privileges, following the principle of least privilege.
Cost Efficiency
Predictable Pricing:
AWS Lightsail offers fixed monthly pricing starting at $3.50/month, including data transfer allowances. This predictable cost structure makes budgeting simple compared to pay-per-use EC2 instances.
Cloudflare Free Tier:
Cloudflare's free tier includes DNS management, SSL certificates, and DDoS protection at no cost. The CDN caching reduces bandwidth usage on your Lightsail instance, potentially allowing you to use a smaller instance size.
Resource Consolidation:
Hosting multiple applications on a single Lightsail instance with Nginx as a reverse proxy maximizes resource utilization. PM2's efficient process management ensures applications use only necessary resources, preventing waste.
No Additional Service Costs:
Unlike managed container services or serverless platforms, Lightsail with Nginx and PM2 has no additional service fees—you only pay for the compute instance.
Troubleshooting
Issue: PM2 Applications Not Starting
Symptoms: PM2 shows applications as "errored" or "stopped"
Solution:
# Check detailed logs
pm2 logs nextjs-app --lines 100
# Check if port is already in use
sudo netstat -tulpn | grep :3000
# Verify Node.js version
node --version
# Restart with verbose output
pm2 delete nextjs-app
pm2 start /var/www/ecosystem.config.js --only nextjs-app
Issue: Nginx 502 Bad Gateway
Symptoms: Nginx returns 502 error when accessing applications
Solution:
# Check if applications are running
pm2 status
# Verify applications are listening on correct ports
curl http://localhost:3000
curl http://localhost:4000
# Check Nginx error logs
sudo tail -f /var/log/nginx/error.log
# Test Nginx configuration
sudo nginx -t
# Restart Nginx
sudo systemctl restart nginx
Issue: GitHub Actions Deployment Fails
Symptoms: GitHub Actions workflow shows SSH connection errors
Solution:
- Verify SSH key is correctly added to GitHub Secrets
- Check Lightsail firewall allows SSH from GitHub Actions IPs
- Test SSH connection manually:
ssh -i myapp-github-actions ubuntu@YOUR_LIGHTSAIL_IP
- Ensure
authorized_keysfile has correct permissions:
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys
Issue: Cloudflare SSL Errors
Symptoms: "Too many redirects" or "SSL handshake failed"
Solution:
- Check Cloudflare SSL mode matches your Nginx configuration:
- Flexible: No SSL on Lightsail (HTTP only)
- Full: Self-signed certificate on Lightsail
- Full (Strict): Cloudflare Origin Certificate on Lightsail
- Disable "Always Use HTTPS" temporarily in Cloudflare
- Check Nginx SSL certificate paths are correct
- Verify Nginx is listening on port 443:
sudo netstat -tulpn | grep :443
Issue: High Memory Usage
Symptoms: PM2 apps restarting frequently due to memory limits
Solution:
# Check memory usage
pm2 status
free -h
# Adjust max_memory_restart in ecosystem.config.js
# Reduce number of instances if needed
# Consider upgrading Lightsail plan
# Monitor memory over time
pm2 monit
Monitoring and Maintenance
PM2 Monitoring:
# View real-time monitoring
pm2 monit
# View process list
pm2 status
# View logs
pm2 logs
# Flush logs
pm2 flush
Nginx Monitoring:
# Check access logs
sudo tail -f /var/log/nginx/access.log
# Check error logs
sudo tail -f /var/log/nginx/error.log
# Check Nginx status
sudo systemctl status nginx
System Monitoring:
# Check disk space
df -h
# Check memory usage
free -h
# Check CPU usage
top
# Check running processes
ps aux
Regular Maintenance Tasks:
- Update system packages monthly:
sudo apt update && sudo apt upgrade -y
- Rotate logs to prevent disk space issues:
pm2 install pm2-logrotate
pm2 set pm2-logrotate:max_size 10M
pm2 set pm2-logrotate:retain 7
- Monitor Lightsail metrics in AWS console
- Review Cloudflare analytics for traffic patterns
- Set up CloudWatch alarms for Lightsail instance health
Next Steps
After successfully deploying your applications, consider these enhancements:
Caching Layer:
- Install Redis for session storage and caching
- Configure Nginx caching for static assets
- Use Cloudflare's cache rules for optimal CDN performance
Monitoring and Logging:
- Set up PM2 Plus for advanced monitoring
- Configure CloudWatch Logs for centralized logging
- Set up Uptime monitoring with services like UptimeRobot
Backup Strategy:
- Configure Lightsail automatic snapshots
- Set up database backup scripts
- Store critical data in S3
Scaling Considerations:
- Use Lightsail load balancer with multiple instances for high availability
- Migrate to ECS or EC2 Auto Scaling Groups for advanced scaling needs
- Consider using Amazon CloudFront in addition to Cloudflare for AWS-native CDN
Security Hardening:
- Configure Cloudflare WAF rules for application protection
- Implement rate limiting at multiple layers
- Regular security updates and vulnerability scanning
Summary
In this tutorial, we successfully deployed Next.js and Node.js applications on AWS Lightsail using a production-ready stack. By combining Nginx as a reverse proxy, PM2 for robust process management, GitHub Actions for automated CI/CD, and Cloudflare for DNS and SSL management, we created a reliable, secure, and cost-effective deployment pipeline.
Top comments (0)