Nginx Reverse Proxy for Node.js: Complete Config Guide
Learn how to configure Nginx as a reverse proxy in front of Node.js applications. Master WebSocket support, load balancing, SSL termination, gzip compression, rate limiting, security headers, and avoid common configuration mistakes.
Why put Nginx in front of Node.js?
Node.js is excellent for building fast HTTP servers, but it has limitations. A single Node process can crash, port 80/443 require root privileges, and raw Node.js doesn't handle SSL, gzip, or load balancing natively.
Nginx sits between your users and Node.js. It terminates SSL, compresses responses, handles static files, rate limits requests, and forwards traffic to one or more Node instances. If a Node process crashes, Nginx keeps serving while you restart it.
1Basic reverse proxy configuration
The simplest Nginx setup forwards all traffic to a single Node.js process:
server {
listen 80;
server_name example.com;
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;
}
}This config listens on port 80 and forwards all requests to http://localhost:3000. The proxy headers preserve the original client IP and request info so your Node app sees the real request.
2WebSocket proxy configuration
WebSockets require connection upgrades. The key directives are proxy_http_version 1.1, Upgrade, and Connection:
upstream nodejs_app {
server localhost:3000;
}
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://nodejs_app;
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;
}
}The Upgrade: websocket header triggers Nginx to upgrade the HTTP connection to WebSocket. We use an upstream block for cleaner config (reusable in other locations).
3Load balancing multiple Node instances
Run multiple Node.js processes and let Nginx distribute load across them:
upstream nodejs_backend {
least_conn;
server localhost:3000;
server localhost:3001;
server localhost:3002;
server localhost:3003;
keepalive 32;
}
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://nodejs_backend;
proxy_http_version 1.1;
proxy_set_header Connection "";
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;
}
}The least_conn algorithm routes traffic to the server with the fewest active connections. keepalive 32 reuses connections for better performance.
Use a process manager like PM2 to start 4 instances: pm2 start app.js -i 4
4SSL/TLS termination
Nginx terminates SSL, decrypts requests, and forwards plain HTTP to Node.js:
server {
listen 80;
server_name example.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
location / {
proxy_pass http://localhost:3000;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Forwarded-Host $server_name;
proxy_set_header Host $host;
}
}The first block redirects HTTP to HTTPS. The second handles HTTPS with Let's Encrypt certificates. Node.js never sees SSL—it only talks HTTP. Set X-Forwarded-Proto: https so your app knows requests are encrypted.
5Gzip compression
Add gzip compression to reduce response sizes (add to http block in nginx.conf):
http {
gzip on;
gzip_vary on;
gzip_types text/plain text/css text/xml text/javascript
application/x-javascript application/xml+rss
application/json application/javascript;
gzip_min_length 1000;
gzip_comp_level 5;
gzip_disable "msie6";
}This compresses JSON, HTML, CSS, and JavaScript responses. gzip_comp_level 5 balances speed and compression (1-9, higher = more compression but slower).
6Rate limiting
Protect your app from abuse by limiting request rates (add to http block):
http {
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
limit_req_zone $binary_remote_addr zone=login_limit:10m rate=5r/m;
server {
location /api/ {
limit_req zone=api_limit burst=20 nodelay;
proxy_pass http://localhost:3000;
}
location /login {
limit_req zone=login_limit burst=5 nodelay;
proxy_pass http://localhost:3000;
}
}
}rate=10r/s allows 10 requests per second. burst=20 allows brief spikes. Requests over the limit get a 429 status.
7Security headers
Add security headers in your server block:
server {
listen 443 ssl;
server_name example.com;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "no-referrer-when-downgrade" always;
add_header Content-Security-Policy "default-src 'self'" always;
location / {
proxy_pass http://localhost:3000;
}
}These headers protect against clickjacking, MIME type sniffing, XSS, and force HTTPS. Adjust CSP rules based on your app's needs.
Common configuration errors
Cause: Node.js isn't running, crashed, or unreachable on the proxy_pass address.
Fix: Check Node.js is listening (netstat -tlnp | grep 3000). Verify proxy_pass points to the right host:port. Check firewall rules.
Cause: Missing Upgrade and Connection headers, or using HTTP/1.0.
Fix: Add proxy_http_version 1.1 and the Upgrade/Connection headers. Never use proxy_http_version 1.0 for WebSockets.
Cause: Missing X-Real-IP or X-Forwarded-For headers.
Fix: Add all proxy_set_header directives. In Express, enable trust proxy: app.set('trust proxy', 1)
Cause: Nginx proxy timeout is too short (default 60s).
Fix: Increase timeouts: proxy_connect_timeout 600s; proxy_send_timeout 600s; proxy_read_timeout 600s;
Cause: Too many keepalive connections or buffer sizes too large.
Fix: Reduce keepalive 32 to keepalive 16. Lower proxy_buffer_size and proxy_buffers.
Complete production Nginx config
A robust Nginx configuration for a production Node.js app:
upstream nodejs_backend {
least_conn;
server localhost:3000;
server localhost:3001;
keepalive 32;
}
server {
listen 80;
server_name example.com www.example.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name example.com www.example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
add_header Strict-Transport-Security "max-age=31536000" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
gzip on;
gzip_types text/plain text/css application/json application/javascript;
gzip_min_length 1000;
location / {
proxy_pass http://nodejs_backend;
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_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
location /static/ {
alias /var/www/app/public/;
expires 30d;
}
}How DeployWise auto-generates Nginx configs
Manual Nginx configuration is powerful but error-prone. DeployWise generates optimal Nginx configs automatically when you deploy:
No Nginx knowledge required — DeployWise handles the hard parts while giving you full control if you need it.
Ready to deploy Node.js properly?
Let DeployWise handle Nginx configuration, load balancing, and SSL. One click to production.
Deploy Your App Now