Nginx for Node.js Apps
Nginx as a reverse proxy in front of Node.js (Next.js, Express, NestJS) handles SSL termination, static asset serving, load balancing, and protects the application server from direct internet exposure.
Reverse Proxy Configuration
# /etc/nginx/sites-enabled/nextjs-app.conf
server {
listen 80;
server_name myapp.com www.myapp.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name myapp.com www.myapp.com;
ssl_certificate /etc/letsencrypt/live/myapp.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/myapp.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
# Security headers
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Content-Type-Options nosniff always;
add_header X-Frame-Options DENY always;
# Next.js static assets — serve directly (bypass Node.js entirely)
location /_next/static/ {
alias /home/deploy/myapp/.next/static/;
expires 1y;
add_header Cache-Control "public, immutable";
access_log off;
}
# Public folder
location /public/ {
alias /home/deploy/myapp/public/;
expires 30d;
add_header Cache-Control "public";
}
# Proxy everything else to Next.js app (port 3000)
location / {
proxy_pass http://127.0.0.1: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 (Next.js HMR, Socket.io, etc.)
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# Timeouts
proxy_read_timeout 60s;
proxy_connect_timeout 10s;
proxy_send_timeout 60s;
# Buffer settings
proxy_buffering on;
proxy_buffer_size 4k;
proxy_buffers 8 4k;
}
}Load Balancing
# upstream block — defines a pool of backend servers
upstream nodejs_cluster {
# Default: round-robin (requests cycle through servers)
server 127.0.0.1:3000;
server 127.0.0.1:3001;
server 127.0.0.1:3002;
# keepalive — reuse connections to upstream (big performance win)
keepalive 32;
}
# Least connections — send to server with fewest active connections
upstream nodejs_leastconn {
least_conn;
server 127.0.0.1:3000;
server 127.0.0.1:3001;
}
# IP hash — sticky sessions (same client -> same server)
upstream nodejs_sticky {
ip_hash;
server 127.0.0.1:3000;
server 127.0.0.1:3001;
}
# Weighted — send 3x more traffic to first server
upstream nodejs_weighted {
server 127.0.0.1:3000 weight=3;
server 127.0.0.1:3001 weight=1;
}
# With health checks and fallback
upstream nodejs_resilient {
server 127.0.0.1:3000;
server 127.0.0.1:3001;
server 127.0.0.1:3002 backup; # only used if others are down
# Mark server down after 3 failures in 30s, retry after 10s
# (add to each server line:)
# max_fails=3 fail_timeout=30s
}
server {
listen 443 ssl http2;
server_name myapp.com;
# ... ssl config ...
location / {
proxy_pass http://nodejs_cluster;
proxy_http_version 1.1;
proxy_set_header Connection ""; # required for keepalive upstream
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}Rate Limiting
# Define rate limit zones in http {} block (outside server {})
# $binary_remote_addr — compact IP representation (4 bytes)
# zone=name:size — shared memory; 1MB ~= 16,000 IPs
http {
# General API: 10 requests/second per IP
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
# Auth endpoints: 5 requests/minute per IP (stricter)
limit_req_zone $binary_remote_addr zone=login:10m rate=5r/m;
# Return 429 instead of default 503 when rate limited
limit_req_status 429;
limit_conn_status 429;
}
server {
# ...
# API routes: allow burst of 20 requests, process immediately (nodelay)
location /api/ {
limit_req zone=api burst=20 nodelay;
proxy_pass http://nodejs_cluster;
}
# Auth routes: strict, no burst
location /api/auth/ {
limit_req zone=login burst=3;
proxy_pass http://nodejs_cluster;
}
# Limit concurrent connections per IP
limit_conn_zone $binary_remote_addr zone=perip:10m;
location /download/ {
limit_conn perip 5; # max 5 simultaneous downloads per IP
limit_rate 500k; # throttle bandwidth to 500KB/s
proxy_pass http://nodejs_cluster;
}
}WebSocket Proxying
# WebSocket connections require the Upgrade and Connection headers
# Nginx needs to pass these through to upgrade HTTP to WS protocol
# map — dynamically set Connection header based on Upgrade presence
http {
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
}
server {
listen 443 ssl http2;
server_name myapp.com;
# ... ssl config ...
# WebSocket endpoint (Socket.io, ws://, Next.js HMR)
location /socket.io/ {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_cache_bypass $http_upgrade;
# WS connections are long-lived — increase timeouts
proxy_read_timeout 3600s; # 1 hour
proxy_send_timeout 3600s;
}
# Regular HTTP routes
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $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;
}
}Keep your own version of these notes — editable, searchable, and organised by your stack.
Start free