A comprehensive guide to deploying applications on AWS EC2 with Nginx as a reverse proxy.
- Prerequisites
- Launch EC2 Instance
- Connect to EC2 Instance
- Initial Server Setup
- Install Nginx
- Configure Firewall
- Deploy Your Application
- Configure Nginx as Reverse Proxy
- SSL/TLS Configuration with Let's Encrypt
- Domain Configuration
- Performance Optimization
- Monitoring and Logs
- Troubleshooting
Before starting, ensure you have:
- AWS Account with appropriate permissions
- SSH client installed on your local machine
- Domain name (optional, for SSL setup)
- Basic knowledge of Linux commands
- Log in to AWS Console
- Search for "EC2" in the services search bar
- Click on "EC2" to open the dashboard
- Click "Launch Instance" button
- Configure the following:
Name: my-web-server
- Select Ubuntu Server 22.04 LTS (recommended)
- Architecture: 64-bit (x86)
| Use Case | Instance Type | vCPU | Memory |
|---|---|---|---|
| Testing/Dev | t2.micro | 1 | 1 GB |
| Small Apps | t2.small | 1 | 2 GB |
| Medium Apps | t2.medium | 2 | 4 GB |
| Production | t3.medium+ | 2+ | 4+ GB |
- Click "Create new key pair"
- Name:
my-ec2-key - Key pair type: RSA
- Private key format: .pem (for Linux/Mac) or .ppk (for Windows/PuTTY)
- Click "Create key pair" and save the file securely
- Click "Edit"
- Configure Security Group:
Security Group Name: web-server-sg
Inbound Rules:
- SSH (22) : Source = My IP
- HTTP (80) : Source = Anywhere (0.0.0.0/0)
- HTTPS (443) : Source = Anywhere (0.0.0.0/0)
- Custom TCP : Port 3000-9000 (for app servers, optional)
- Root volume: 20-30 GB gp3 (General Purpose SSD)
Click "Launch Instance" and wait for the instance to start.
# Set correct permissions for key file
chmod 400 my-ec2-key.pem
# Connect to instance
ssh -i "my-ec2-key.pem" ubuntu@<your-ec2-public-ip>- Select your instance in EC2 Dashboard
- Click "Connect" button
- Choose "EC2 Instance Connect" tab
- Click "Connect"
- Convert .pem to .ppk using PuTTYgen
- Open PuTTY
- Host:
ubuntu@<your-ec2-public-ip> - Connection > SSH > Auth > Browse for .ppk file
- Click "Open"
# Update package lists
sudo apt update
# Upgrade installed packages
sudo apt upgrade -y
# Install essential tools
sudo apt install -y curl wget git vim htop unzip# Check current timezone
timedatectl
# Set timezone (example: Asia/Dhaka)
sudo timedatectl set-timezone Asia/Dhaka# Create 2GB swap file
sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
# Make swap permanent
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
# Verify swap
free -h# Install Nginx
sudo apt install nginx -y
# Start and enable Nginx
sudo systemctl start nginx
sudo systemctl enable nginx
# Check status
sudo systemctl status nginxOpen your browser and navigate to:
http://<your-ec2-public-ip>
You should see the "Welcome to nginx!" page.
/etc/nginx/
├── nginx.conf # Main configuration file
├── sites-available/ # Available site configurations
├── sites-enabled/ # Enabled site configurations (symlinks)
├── conf.d/ # Additional configurations
└── snippets/ # Reusable configuration snippets
/var/www/ # Web root directory
/var/log/nginx/ # Log files (access.log, error.log)
# Enable UFW
sudo ufw enable
# Allow SSH (IMPORTANT: Do this first!)
sudo ufw allow ssh
# OR
sudo ufw allow 22
# Allow Nginx HTTP
sudo ufw allow 'Nginx HTTP'
# Allow Nginx HTTPS
sudo ufw allow 'Nginx HTTPS'
# Allow both HTTP and HTTPS
sudo ufw allow 'Nginx Full'
# Check status
sudo ufw statusStatus: active
To Action From
-- ------ ----
22/tcp ALLOW Anywhere
Nginx Full ALLOW Anywhere
22/tcp (v6) ALLOW Anywhere (v6)
Nginx Full (v6) ALLOW Anywhere (v6)
# Install Node.js (using NodeSource)
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt install -y nodejs
# Verify installation
node --version
npm --version
# Create application directory
sudo mkdir -p /var/www/myapp
sudo chown -R $USER:$USER /var/www/myapp
# Clone your repository
cd /var/www/myapp
git clone https://github.com/yourusername/your-repo.git .
# Install dependencies
npm install
# Install PM2 for process management
sudo npm install -g pm2
# Start application with PM2
pm2 start app.js --name "myapp"
# Save PM2 configuration
pm2 save
# Setup PM2 to start on boot
pm2 startup systemd
# Run the command it outputs# Install Python and pip
sudo apt install -y python3 python3-pip python3-venv
# Create application directory
sudo mkdir -p /var/www/myapp
sudo chown -R $USER:$USER /var/www/myapp
# Clone your repository
cd /var/www/myapp
git clone https://github.com/yourusername/your-repo.git .
# Create virtual environment
python3 -m venv venv
source venv/bin/activate
# Install dependencies
pip install -r requirements.txt
# Install Gunicorn
pip install gunicorn
# Run with Gunicorn
gunicorn --bind 0.0.0.0:8000 app:app# Create web directory
sudo mkdir -p /var/www/mysite
sudo chown -R $USER:$USER /var/www/mysite
# Copy your static files
cd /var/www/mysite
# Place your HTML, CSS, JS files hereCreate a new server block:
sudo nano /etc/nginx/sites-available/myappAdd the following configuration:
server {
listen 80;
server_name your-domain.com www.your-domain.com;
# Or use: server_name _; for IP-based access
# Logs
access_log /var/log/nginx/myapp_access.log;
error_log /var/log/nginx/myapp_error.log;
# Proxy settings
location / {
proxy_pass http://localhost:3000; # Your app 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;
}
}server {
listen 80;
server_name your-domain.com www.your-domain.com;
root /var/www/mysite;
index index.html index.htm;
location / {
try_files $uri $uri/ =404;
}
# Cache static assets
location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ {
expires 30d;
add_header Cache-Control "public, immutable";
}
}# Create symbolic link
sudo ln -s /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/
# Remove default site (optional)
sudo rm /etc/nginx/sites-enabled/default
# Test configuration
sudo nginx -t
# Reload Nginx
sudo systemctl reload nginx# Install Certbot and Nginx plugin
sudo apt install certbot python3-certbot-nginx -y# Obtain certificate (replace with your domain)
sudo certbot --nginx -d your-domain.com -d www.your-domain.com
# Follow the prompts:
# - Enter email address
# - Agree to terms
# - Choose whether to redirect HTTP to HTTPS (recommended: Yes)# Test renewal process
sudo certbot renew --dry-run
# Check renewal timer
sudo systemctl status certbot.timerYour Nginx config will be automatically updated, but here's what it looks like:
server {
listen 80;
server_name your-domain.com www.your-domain.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name your-domain.com www.your-domain.com;
# SSL certificates (managed by Certbot)
ssl_certificate /etc/letsencrypt/live/your-domain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/your-domain.com/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
# Your application
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;
}
}# From EC2 Dashboard or run:
curl http://checkip.amazonaws.comGo to your domain registrar (GoDaddy, Namecheap, Route53, etc.) and add:
| Type | Name | Value | TTL |
|---|---|---|---|
| A | @ | <EC2-Public-IP> |
300 |
| A | www | <EC2-Public-IP> |
300 |
- Create a Hosted Zone for your domain
- Add A record pointing to EC2 IP
- Update nameservers at your registrar
# Allocate Elastic IP from EC2 Dashboard:
# 1. Go to EC2 > Elastic IPs
# 2. Click "Allocate Elastic IP address"
# 3. Click "Allocate"
# 4. Select the IP > Actions > Associate Elastic IP address
# 5. Select your instance
# 6. Click "Associate"Edit main Nginx config:
sudo nano /etc/nginx/nginx.confuser www-data;
worker_processes auto;
pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;
events {
worker_connections 1024;
multi_accept on;
use epoll;
}
http {
# Basic Settings
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
server_tokens off;
# Buffer Settings
client_body_buffer_size 10K;
client_header_buffer_size 1k;
client_max_body_size 50M;
large_client_header_buffers 2 1k;
# Gzip Compression
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types text/plain text/css text/xml application/json application/javascript application/rss+xml application/atom+xml image/svg+xml;
# Rate Limiting
limit_req_zone $binary_remote_addr zone=one:10m rate=10r/s;
include /etc/nginx/mime.types;
default_type application/octet-stream;
# Logging
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
}Add to your server block:
# Browser caching for static files
location ~* \.(jpg|jpeg|png|gif|ico|css|js|pdf|woff|woff2)$ {
expires 30d;
add_header Cache-Control "public, no-transform";
}
# Proxy caching
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=my_cache:10m max_size=1g inactive=60m use_temp_path=off;
location / {
proxy_cache my_cache;
proxy_cache_valid 200 60m;
proxy_cache_use_stale error timeout http_500 http_502 http_503 http_504;
# ... other proxy settings
}# Access logs
sudo tail -f /var/log/nginx/access.log
# Error logs
sudo tail -f /var/log/nginx/error.log
# Application-specific logs
sudo tail -f /var/log/nginx/myapp_access.log
sudo tail -f /var/log/nginx/myapp_error.log# Real-time system monitor
htop
# Disk usage
df -h
# Memory usage
free -m
# Nginx process status
ps aux | grep nginx# Service status
sudo systemctl status nginx
# Active connections
curl http://localhost/nginx_statusAdd to your server block:
location /nginx_status {
stub_status on;
allow 127.0.0.1;
deny all;
}# Check for syntax errors
sudo nginx -t
# Check error logs
sudo journalctl -u nginx
sudo tail -50 /var/log/nginx/error.log
# Check if port 80 is in use
sudo lsof -i :80# Check if your application is running
sudo systemctl status your-app
pm2 status
# Check if app is listening on correct port
sudo netstat -tlnp | grep :3000
# Check Nginx proxy settings
# Ensure proxy_pass port matches your app port# Check file permissions
ls -la /var/www/myapp/
# Fix permissions
sudo chown -R www-data:www-data /var/www/myapp/
sudo chmod -R 755 /var/www/myapp/# Check security group rules in AWS Console
# Ensure ports 80/443 are open
# Check UFW
sudo ufw status
# Check if Nginx is listening
sudo netstat -tlnp | grep nginx# Renew certificate manually
sudo certbot renew
# Check certificate expiry
sudo certbot certificates
# Test SSL configuration
curl -I https://your-domain.com# Nginx commands
sudo systemctl start nginx
sudo systemctl stop nginx
sudo systemctl restart nginx
sudo systemctl reload nginx
sudo nginx -t # Test configuration
sudo nginx -s reload # Reload without downtime
# PM2 commands (Node.js)
pm2 list # List all processes
pm2 logs # View logs
pm2 restart all # Restart all processes
pm2 monit # Monitor processes
# System commands
sudo reboot # Restart server
sudo shutdown -h now # Shutdown server# Enable automatic security updates
sudo apt install unattended-upgrades
sudo dpkg-reconfigure unattended-upgrades# Install Fail2Ban
sudo apt install fail2ban -y
# Create local config
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
# Enable and start
sudo systemctl enable fail2ban
sudo systemctl start fail2bansudo nano /etc/ssh/sshd_config
# Set these values:
PermitRootLogin no
PasswordAuthentication no
# Restart SSH
sudo systemctl restart sshdAdd to your Nginx server block:
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "no-referrer-when-downgrade" always;
add_header Content-Security-Policy "default-src 'self' http: https: data: blob: 'unsafe-inline'" always;| Task | Command |
|---|---|
| SSH to server | ssh -i key.pem ubuntu@IP |
| Restart Nginx | sudo systemctl restart nginx |
| Test Nginx config | sudo nginx -t |
| View access logs | sudo tail -f /var/log/nginx/access.log |
| View error logs | sudo tail -f /var/log/nginx/error.log |
| Renew SSL | sudo certbot renew |
| Check disk space | df -h |
| Check memory | free -m |
| List PM2 apps | pm2 list |
| Restart PM2 app | pm2 restart app-name |
- Nginx Documentation
- AWS EC2 Documentation
- Let's Encrypt Documentation
- PM2 Documentation
- Ubuntu Server Guide
Author: Sahinur Last Updated: January 2026 License: MIT