Nginx Reverse Proxy for Node.js (Config Snippet + Test with curl)

Running a Node.js app behind Nginx is a battle-tested pattern: Nginx handles TLS, static files, gzip, and connection upgrades (for WebSockets), while your Node app focuses on business logic. This guide gives you a clean, production-ready Nginx config and shows how to test it with curl.


What you’ll set up

  • A Node.js app listening on 127.0.0.1:3000
  • Nginx reverse proxy on port 80/443
  • WebSocket support
  • Gzip compression & basic hardening
  • Simple tests with curl

Works on Ubuntu/Debian and CentOS/RHEL. Adjust paths if your distro uses different Nginx locations.


1) Minimal Node.js app (for testing)

Create a tiny server to verify the proxy:

mkdir -p /var/www/node-app && cd /var/www/node-app
cat > server.js <<'EOF'
const http = require('http');

const server = http.createServer((req, res) => {
  // Simple health route
  if (req.url === '/health') {
    res.writeHead(200, { 'Content-Type': 'application/json' });
    return res.end(JSON.stringify({ ok: true, via: 'node', time: new Date().toISOString() }));
  }

  // Echo headers (useful for proxy tests)
  res.writeHead(200, { 'Content-Type': 'application/json' });
  res.end(JSON.stringify({
    url: req.url,
    headers: req.headers,
    message: 'Hello from Node behind Nginx!'
  }));
});

server.listen(3000, '127.0.0.1', () => {
  console.log('Node app listening on http://127.0.0.1:3000');
});
EOF

node server.js

You should see: Node app listening on http://127.0.0.1:3000


2) (Optional) Keep Node running with systemd

sudo tee /etc/systemd/system/node-app.service >/dev/null <<'EOF'
[Unit]
Description=Node.js Example App
After=network.target

[Service]
WorkingDirectory=/var/www/node-app
ExecStart=/usr/bin/node server.js
Restart=always
RestartSec=3
Environment=NODE_ENV=production
User=www-data
Group=www-data

[Install]
WantedBy=multi-user.target
EOF

sudo systemctl daemon-reload
sudo systemctl enable --now node-app
sudo systemctl status node-app --no-pager

On CentOS/RHEL, the Node path is usually /usr/bin/node too; user/group may be nginx instead of www-data.


3) Nginx reverse proxy config (HTTP)

Create a site config (Debian/Ubuntu style):

sudo mkdir -p /etc/nginx/sites-available /etc/nginx/sites-enabled
sudo tee /etc/nginx/sites-available/node-proxy.conf >/dev/null <<'EOF'
# Replace example.com with your domain
server {
    listen 80;
    server_name example.com www.example.com;

    # Gzip for JSON/JS/CSS
    gzip on;
    gzip_types text/plain application/json application/javascript text/css;
    gzip_min_length 1024;

    # Proxy timeouts (tune as needed)
    proxy_connect_timeout 5s;
    proxy_send_timeout 60s;
    proxy_read_timeout 60s;
    send_timeout 60s;

    # Forward real client IPs
    set $upstream http://127.0.0.1:3000;

    location / {
        proxy_pass $upstream;

        # Preserve Host and IP info
        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_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;

        # Don't buffer SSE/WebSockets too aggressively
        proxy_buffering off;
    }

    # Health path can be cached off for clarity
    location = /health {
        proxy_pass $upstream;
        proxy_set_header Host $host;
        proxy_buffering off;
    }

    # Basic hardening
    add_header X-Frame-Options SAMEORIGIN always;
    add_header X-Content-Type-Options nosniff always;

    # Map for Connection upgrade (place in http{} block if in main nginx.conf)
}
EOF

Create a small map to handle Connection header for WS upgrades. Put this once in your global http {} block (e.g., /etc/nginx/nginx.conf), outside the server block:

map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}

Enable and test:

sudo ln -s /etc/nginx/sites-available/node-proxy.conf /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx

CentOS/RHEL path: often a single /etc/nginx/nginx.conf with server {} blocks in /etc/nginx/conf.d/*.conf. If so, save as /etc/nginx/conf.d/node-proxy.conf and skip the symlink step.


4) Add HTTPS (Let’s Encrypt)

If your DNS points to the server, you can set up TLS quickly:

# Install Certbot
# Ubuntu/Debian:
sudo apt-get update && sudo apt-get install -y certbot python3-certbot-nginx

# CentOS/RHEL (Stream 8/9; may be 'dnf' and package names differ slightly):
# sudo dnf install -y certbot python3-certbot-nginx

# Obtain and auto-configure SSL for the server block with server_name example.com
sudo certbot --nginx -d example.com -d www.example.com

# Auto renewal (usually installed by default)
sudo systemctl status certbot.timer --no-pager

Certbot will add listen 443 ssl http2; and ssl_certificate lines for you.


5) Test with 

curl

A. Test that Nginx reaches Node

# Replace example.com with your domain or server IP (if HTTP only for now)
curl -i http://example.com/health

Expected:

  • HTTP/1.1 200 OK (or HTTP/2 200 if HTTPS)
  • JSON body like: {“ok”:true,”via”:”node”,”time”:”…”}
  • Response header server: nginx (from Nginx) and body says it came from Node

B. Check headers forwarded by Nginx

curl -i http://example.com/ -H "X-Demo: test123"

Look for:

  • Host: example.com
  • x-demo: test123
  • x-forwarded-proto: http (or https if TLS enabled)
  • x-real-ip set to your client IP

C. Test HTTPS (after Certbot)

curl -I https://example.com/health

You should see:

  • HTTP/2 200
  • TLS certificate fields if you add -v

D. (Optional) WebSocket sanity test

If your Node app upgrades connections at /ws, you can verify the handshake:

curl -i -N \
  -H "Connection: Upgrade" \
  -H "Upgrade: websocket" \
  -H "Host: example.com" \
  -H "Origin: https://example.com" \
  http://example.com/ws

You should get 101 Switching Protocols if your backend handles WS.


6) Common pitfalls & fixes

  • 502 Bad Gateway
    • Node app not running or wrong upstream port/host.
    • SELinux (CentOS) may block Nginx from proxying: setsebool -P httpd_can_network_connect 1
  • WebSockets not upgrading
    • Missing proxy_http_version 1.1, Upgrade, and Connection headers.
    • The map $http_upgrade $connection_upgrade must be in http{}.
  • Wrong client IP
    • Ensure proxy_set_header X-Real-IP $remote_addr; and X-Forwarded-For.
  • TLS issues
    • DNS not pointing to server before running Certbot.
    • Port 80/443 blocked by firewall.

7) Full example (drop-in) for 

/etc/nginx/conf.d/node-proxy.conf

# Place this map ONCE in /etc/nginx/nginx.conf inside http { }:
# map $http_upgrade $connection_upgrade { default upgrade; '' close; }

server {
    listen 80;
    server_name example.com www.example.com;

    gzip on;
    gzip_types text/plain application/json application/javascript text/css;
    gzip_min_length 1024;

    set $upstream http://127.0.0.1:3000;

    location / {
        proxy_pass $upstream;
        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_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;

        proxy_buffering off;
        proxy_connect_timeout 5s;
        proxy_send_timeout 60s;
        proxy_read_timeout 60s;
    }

    location = /health {
        proxy_pass $upstream;
        proxy_set_header Host $host;
        proxy_buffering off;
    }

    add_header X-Frame-Options SAMEORIGIN always;
    add_header X-Content-Type-Options nosniff always;
}

Reload Nginx after changes:

sudo nginx -t && sudo systemctl reload nginx

Final checklist

  • Node app runs on 127.0.0.1:3000 (or your chosen port)
  • Nginx config enabled and tested (nginx -t)
  • Health endpoint returns 200 via Nginx
  • TLS installed with Certbot (optional but recommended)
  • curl tests show correct headers and status codes

Tags: Nginx, Node.js, Reverse Proxy, WebSockets, Linux, CentOS, Ubuntu, DevOps

Meta Description: Learn how to put Node.js behind Nginx with a production-ready reverse proxy config, WebSocket support, and quick tests using curl.


Posted

in

by

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *