How to Configure Nginx as Reverse Proxy for Express.js: Solving Common Proxy_pass Issues


3 views

When setting up Nginx to proxy requests to an Express.js application, you might encounter situations where:

  • Basic Node.js HTTP servers work fine with proxy_pass
  • Express.js applications return no response or incorrect redirects
  • Curl tests on localhost work but public access fails

Here's the proper Nginx configuration that handles Express apps correctly:

server {
  listen 80;
  listen [::]:80;
  
  server_name services.stefanow.net;

  location /test-express/ {
    proxy_pass http://127.0.0.1:3002/;
    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;
  }
}

The critical configuration elements that make Express work:

# Notice the trailing slashes - crucial for path handling
location /test-express/ {
  proxy_pass http://127.0.0.1:3002/;
  
  # Required for WebSocket support if needed
  proxy_http_version 1.1;
  proxy_set_header Upgrade $http_upgrade;
  proxy_set_header Connection 'upgrade';
  
  # Essential headers for Express to understand the original request
  proxy_set_header Host $host;
  proxy_set_header X-Forwarded-Proto $scheme;
}

Your Express app needs these modifications to work behind Nginx:

const express = require('express');
const app = express();

// Trust the X-Forwarded-* headers
app.set('trust proxy', true);

// Handle the proxied path correctly
app.get('/test-express/', (req, res) => {
  res.send("This works through Nginx proxy");
});

app.listen(3002, "127.0.0.1");

1. Trailing Slash Mismatch
Always include trailing slashes in both location and proxy_pass directives.

2. Missing X-Forwarded Headers
Without X-Forwarded-Proto, Express may generate incorrect redirect URLs.

3. Port Binding
Ensure Express binds to 127.0.0.1, not 0.0.0.0, to prevent direct external access.

Check these when troubleshooting:

# Verify Nginx is passing requests
tail -f /var/log/nginx/error.log

# Test proxy locally
curl -H "Host: services.stefanow.net" http://localhost/test-express/

# Check Express is receiving proper headers
app.use((req, res, next) => {
  console.log(req.headers);
  next();
});

Remember to restart Nginx after configuration changes: sudo systemctl restart nginx

For production environments, consider adding:

proxy_buffering off;
proxy_redirect off;
proxy_read_timeout 300s;

# Cookie path rewriting
proxy_cookie_path / /test-express/;

When setting up Nginx as a reverse proxy for Express.js applications, developers often encounter unexpected behavior even when the configuration appears correct. The fundamental problem stems from how Express handles routes versus how Nginx forwards requests.

Your current Nginx configuration has these key elements:

location /test-express {
  proxy_pass    http://127.0.0.1:3002;
}

While the equivalent setup works for plain Node.js HTTP server:

location /test-http {
  proxy_pass    http://127.0.0.1:3003;
}

Express applications handle routing differently than plain HTTP servers. When you access /test-express through Nginx:

  1. Nginx forwards the request to http://127.0.0.1:3002/test-express
  2. Express receives /test-express as the route path
  3. Your Express app only has routes for / and /index.html

Here's the proper Nginx configuration for Express applications:

location /test-express/ {
  proxy_pass http://127.0.0.1:3002/;
  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;
}

Key modifications:

  • Added trailing slash to location and proxy_pass
  • Included proper HTTP headers
  • Set proxy_http_version for WebSocket compatibility

Modify your Express app to handle the proxy context:

const express = require('express');
const app = express();

// Set trust proxy for correct IP detection
app.set('trust proxy', true);

// Handle root route with context
app.get('/test-express', (req, res) => {
  res.redirect('/test-express/index.html');
});

app.get('/test-express/index.html', (req, res) => {
  res.send("Express working with Nginx proxy!");
});

app.listen(3002, "127.0.0.1");

Verify the setup works properly:

curl -I http://yourdomain.com/test-express/
curl http://yourdomain.com/test-express/index.html
  • Missing trailing slashes in location and proxy_pass
  • Not setting trust proxy in Express
  • Incorrect headers for WebSocket connections
  • Not handling URL encoding properly

For production environments, consider adding:

proxy_read_timeout 90;
proxy_connect_timeout 90;
proxy_send_timeout 90;
proxy_buffers 16 32k;
proxy_buffer_size 64k;
proxy_temp_file_write_size 64k;