DeployWise
HomeGuidesNginx Reverse Proxy for Node.js
NginxNode.jsReverse ProxyDevOpsConfiguration

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.

12 min read
Updated 2026

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.

Key benefits:
Run Node.js on port 3000+, Nginx on ports 80/443
Terminate SSL/TLS at the edge (Nginx handles certificates)
Compress responses with gzip automatically
Load balance traffic across multiple Node instances
Cache static files and API responses
Rate limit and block malicious requests
Add security headers without touching Node.js code
Zero downtime restarts of Node processes

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

502 Bad Gateway 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.

WebSocket connections drop

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.

Node.js sees wrong client IP

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)

Slow responses or timeouts

Cause: Nginx proxy timeout is too short (default 60s).

Fix: Increase timeouts: proxy_connect_timeout 600s; proxy_send_timeout 600s; proxy_read_timeout 600s;

High memory usage

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:

Detects if your app uses WebSockets and enables Upgrade headers
Configures gzip for all text-based responses
Issues free Let's Encrypt SSL certificates
Adds security headers by default
Sets up load balancing if you run multiple instances
Includes sensible default timeouts and buffer sizes
Can customize all settings from the dashboard

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