Step-by-step guide for setting up Nginx to proxy to your application. The definitive practical tutorial. DigitalOcean →
Your Node.js app listens on port 3000. Nginx listens on port 80/443. Nginx receives every HTTP/HTTPS request from the internet and proxies it to your app. The client never connects directly to Node.js.
Benefits of a reverse proxy:
# /etc/nginx/nginx.conf — main config
# /etc/nginx/sites-available/ — available site configs
# /etc/nginx/sites-enabled/ — symlinks to active configs
# /var/log/nginx/access.log — all requests
# /var/log/nginx/error.log — errors
# NEVER edit nginx.conf directly. Create files in sites-available/
# then symlink to sites-enabled/
# /etc/nginx/sites-available/yourapp.com
# Redirect HTTP → HTTPS
server {
listen 80;
server_name yourapp.com www.yourapp.com;
return 301 https://$host$request_uri;
}
# HTTPS server
server {
listen 443 ssl http2;
server_name yourapp.com www.yourapp.com;
# SSL certificates (managed by Certbot)
ssl_certificate /etc/letsencrypt/live/yourapp.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/yourapp.com/privkey.pem;
# TLS settings
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off;
# Security headers
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Content-Type-Options nosniff;
add_header X-Frame-Options DENY;
add_header Referrer-Policy "strict-origin-when-cross-origin";
# Serve static files directly (fast)
location /static/ {
alias /var/www/yourapp/static/;
expires 1y;
add_header Cache-Control "public, immutable";
}
# Proxy everything else to Node.js
location / {
proxy_pass http://localhost:3000;
proxy_http_version 1.1;
# Required headers
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;
# WebSocket support
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# Timeouts
proxy_read_timeout 60s;
proxy_connect_timeout 10s;
proxy_send_timeout 60s;
}
# Custom error pages
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
internal;
}
}
# Create the config file
sudo nano /etc/nginx/sites-available/yourapp.com
# Test for syntax errors (ALWAYS do this before reloading)
sudo nginx -t
# Enable the site (symlink to sites-enabled)
sudo ln -s /etc/nginx/sites-available/yourapp.com \
/etc/nginx/sites-enabled/
# Reload Nginx (zero-downtime config reload)
sudo systemctl reload nginx
# View access logs
sudo tail -f /var/log/nginx/access.log
# View error logs
sudo tail -f /var/log/nginx/error.log
A syntax error in your Nginx config will prevent reload and potentially take down your site. Always run sudo nginx -t before reloading. If it outputs "test is successful", it's safe to reload.
Nginx lets you host multiple apps on one EC2 instance — great for cost savings on small projects:
# /etc/nginx/sites-available/api.yourapp.com
server {
listen 443 ssl http2;
server_name api.yourapp.com;
# ... ssl certs ...
location / {
proxy_pass http://localhost:3001; # API on 3001
}
}
# /etc/nginx/sites-available/admin.yourapp.com
server {
listen 443 ssl http2;
server_name admin.yourapp.com;
# ... ssl certs ...
location / {
proxy_pass http://localhost:3002; # Admin on 3002
}
}