Complete Guide to Nginx Reverse Proxy Node.js Setup for Production
Setting up an nginx reverse proxy for Node.js applications is one of the most critical infrastructure decisions you’ll make for production deployments. We’ve configured this setup dozens of times across projects like QuickLotz WMS (our enterprise warehouse management system) and Vidmation (our AI-powered video automation pipeline), and the performance gains are substantial.
A proper nginx reverse proxy node.js setup provides SSL termination, static file serving, load balancing, and request buffering — all essential for handling real production traffic. Without it, your Node.js app handles every request directly, from serving images to processing API calls, which creates unnecessary overhead.
Why Nginx Reverse Proxy for Node.js?
Node.js excels at handling concurrent connections and asynchronous operations, but it’s not optimized for serving static files or handling slow clients. When we deployed QuickLotz WMS without nginx initially, we saw significant performance degradation under load — the Node.js process was spending cycles serving CSS files instead of processing warehouse operations.
Here’s what nginx brings to the table:
Static File Serving: Nginx serves static assets (CSS, JS, images) directly from disk, freeing your Node.js app to handle dynamic requests only.
SSL Termination: Handle TLS encryption/decryption at the nginx layer, reducing CPU overhead on your Node.js processes.
Request Buffering: Nginx buffers slow client requests before passing them to Node.js, preventing slow clients from tying up your application threads.
Load Balancing: Distribute requests across multiple Node.js instances running on different ports.
Basic Nginx Reverse Proxy Configuration
Let’s start with a minimal nginx reverse proxy node.js setup. First, install nginx:
# Ubuntu/Debian
sudo apt update
sudo apt install nginx
# CentOS/RHEL
sudo yum install nginx
Create a basic configuration file at /etc/nginx/sites-available/your-app:
server {
listen 80;
server_name your-domain.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;
}
}
Enable the site and restart nginx:
sudo ln -s /etc/nginx/sites-available/your-app /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl restart nginx
This basic setup forwards all requests to a Node.js app running on port 3000. The proxy headers ensure your Node.js app receives the original client information.
Production-Grade Configuration with SSL
For production deployments like our Vidmation platform, we need SSL, static file optimization, and proper security headers. Here’s our production nginx reverse proxy node.js setup:
server {
listen 80;
server_name your-domain.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name your-domain.com;
# SSL Configuration
ssl_certificate /path/to/your/fullchain.pem;
ssl_certificate_key /path/to/your/privkey.pem;
ssl_session_timeout 1d;
ssl_session_cache shared:MozTLS:10m;
ssl_session_tickets off;
# Modern SSL configuration
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
ssl_prefer_server_ciphers off;
# Security headers
add_header Strict-Transport-Security "max-age=63072000" always;
add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";
# Gzip compression
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_proxied any;
gzip_comp_level 6;
gzip_types
text/plain
text/css
text/xml
text/javascript
application/json
application/javascript
application/xml+rss
application/atom+xml
image/svg+xml;
# Static files
location /static/ {
alias /var/www/your-app/public/;
expires 1y;
add_header Cache-Control "public, immutable";
}
# API routes with rate limiting
location /api/ {
limit_req zone=api burst=20 nodelay;
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;
# Timeouts
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
# Main 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;
}
}
# Rate limiting zone definition (add to main nginx.conf)
http {
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
}
Load Balancing Multiple Node.js Instances
For high-traffic applications, you’ll want to run multiple Node.js processes. We use this approach for QuickLotz WMS to handle concurrent warehouse operations across multiple facilities.
Define an upstream block in your nginx configuration:
upstream nodejs_backend {
least_conn;
server localhost:3000;
server localhost:3001;
server localhost:3002;
server localhost:3003;
}
server {
listen 443 ssl http2;
server_name your-domain.com;
# SSL and other configurations...
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_cache_bypass $http_upgrade;
}
}
Start multiple Node.js instances using PM2:
npm install -g pm2
# ecosystem.config.js
module.exports = {
apps: [{
name: 'app-3000',
script: 'app.js',
env: {
PORT: 3000,
NODE_ENV: 'production'
}
}, {
name: 'app-3001',
script: 'app.js',
env: {
PORT: 3001,
NODE_ENV: 'production'
}
}, {
name: 'app-3002',
script: 'app.js',
env: {
PORT: 3002,
NODE_ENV: 'production'
}
}, {
name: 'app-3003',
script: 'app.js',
env: {
PORT: 3003,
NODE_ENV: 'production'
}
}]
};
pm2 start ecosystem.config.js
pm2 save
pm2 startup
WebSocket Support Configuration
Modern applications often use WebSockets for real-time features. Our ClawdHub terminal IDE uses WebSockets extensively for real-time AI agent monitoring. Here’s how to configure nginx for WebSocket support:
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
# Other configurations...
location /socket.io/ {
proxy_pass http://nodejs_backend;
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;
# WebSocket-specific timeouts
proxy_read_timeout 86400;
proxy_send_timeout 86400;
}
}
Performance Optimization and Monitoring
Beyond basic setup, proper nginx reverse proxy node.js setup requires ongoing optimization. Here are the key areas we focus on:
Buffer Sizes and Timeouts
server {
# Buffer settings
proxy_buffering on;
proxy_buffer_size 128k;
proxy_buffers 4 256k;
proxy_busy_buffers_size 256k;
# Client settings
client_max_body_size 50M;
client_body_buffer_size 128k;
# Timeout settings
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
Logging and Monitoring
Configure detailed logging for troubleshooting:
http {
log_format detailed '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'rt=$request_time uct="$upstream_connect_time" '
'uht="$upstream_header_time" urt="$upstream_response_time"';
access_log /var/log/nginx/access.log detailed;
error_log /var/log/nginx/error.log warn;
}
Health Checks and Circuit Breakers
Implement health checks to automatically remove failed Node.js instances:
upstream nodejs_backend {
server localhost:3000 max_fails=3 fail_timeout=30s;
server localhost:3001 max_fails=3 fail_timeout=30s;
server localhost:3002 max_fails=3 fail_timeout=30s backup;
}
Security Hardening
Production deployments require additional security measures. Here’s our security-hardened configuration:
server {
# Hide nginx version
server_tokens off;
# Prevent clickjacking
add_header X-Frame-Options SAMEORIGIN;
# Prevent MIME type sniffing
add_header X-Content-Type-Options nosniff;
# Enable XSS protection
add_header X-XSS-Protection "1; mode=block";
# HSTS header
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
# Prevent access to hidden files
location ~ /\. {
deny all;
access_log off;
log_not_found off;
}
# Prevent access to backup files
location ~ ~$ {
deny all;
access_log off;
log_not_found off;
}
}
Troubleshooting Common Issues
When implementing nginx reverse proxy node.js setup, you’ll encounter several common issues:
502 Bad Gateway Errors
This usually means nginx can’t connect to your Node.js app. Check:
- Is your Node.js app running on the specified port?
- Is the port accessible (not blocked by firewall)?
- Are the proxy_pass URLs correct?
# Test Node.js app directly
curl localhost:3000
# Check nginx configuration
sudo nginx -t
# Check nginx error logs
sudo tail -f /var/log/nginx/error.log
WebSocket Connection Failures
Ensure you have the correct headers for WebSocket upgrade:
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
Performance Issues
Monitor upstream response times and adjust buffer sizes:
# Monitor nginx access logs
tail -f /var/log/nginx/access.log | grep -E "rt=[0-9]+\.[0-9]+"
# Check system resources
htop
iotop
Integration with CI/CD Pipelines
For automated deployments, integrate nginx configuration updates with your CI/CD pipeline. Here’s how we handle this in our GitHub Actions CI/CD setup:
# .github/workflows/deploy.yml
- name: Update Nginx Configuration
run: |
sudo cp nginx/production.conf /etc/nginx/sites-available/myapp
sudo nginx -t
sudo systemctl reload nginx
Docker and Containerized Deployments
When using Docker, nginx reverse proxy node.js setup becomes even more important for container orchestration:
# docker-compose.yml
version: '3.8'
services:
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
- ./ssl:/etc/ssl/certs
depends_on:
- app
app:
build: .
expose:
- "3000"
environment:
- NODE_ENV=production
- PORT=3000
Key Takeaways
- Always use nginx in production: Direct Node.js exposure to the internet creates performance and security vulnerabilities
- Configure proper headers: X-Real-IP, X-Forwarded-For, and X-Forwarded-Proto are essential for maintaining client information
- Implement SSL termination: Let nginx handle TLS encryption to reduce Node.js CPU overhead
- Use load balancing: Multiple Node.js instances behind nginx provide better performance and availability
- Monitor upstream health: Configure health checks and automatic failover for robust deployments
- Optimize buffer sizes: Proper proxy buffering prevents slow clients from blocking your Node.js processes
- Enable compression: Gzip compression at the nginx layer significantly reduces bandwidth usage
- Implement rate limiting: Protect your Node.js app from abuse with nginx’s built-in rate limiting
The nginx reverse proxy node.js setup we’ve outlined here handles the infrastructure challenges we’ve encountered across dozens of production deployments. From high-traffic enterprise applications to real-time AI agent orchestration, this configuration provides the performance and reliability your Node.js applications need.
If you’re building production Node.js applications and need help with infrastructure setup, deployment automation, or performance optimization, we’d love to help. Reach out to discuss your project.
More from the blog
Need reliable infrastructure?
We build CI/CD pipelines, monitoring systems, and deployment infrastructure engineered for reliability.
Get our AI implementation playbook
A practical guide to evaluating, planning, and deploying AI in your business. Free, no spam.
Check your inbox.
Something went wrong. Please try again.