Back to Blog
DevOpsJune 26, 20264 min read

Nginx Reverse Proxy Configuration: Optimizing Header Forwarding

Master Nginx reverse proxy header forwarding. Learn to correctly pass X-Forwarded-For and Host headers for Node.js and Laravel apps to fix IP and SSL issues.

nginxreverse-proxydevopslaravelnodejsweb-servers

Last month, I spent about three hours debugging why a Laravel application was generating absolute URLs with http instead of https after we moved it behind an Nginx load balancer. The app was receiving the request, but it couldn't tell it was behind a proxy, causing the internal URL generator to default to the insecure protocol.

When you set up an nginx reverse proxy, the backend application stops seeing the actual client's IP address and instead sees the IP of the Nginx server. If you don't pass the right headers, your application logs will be useless, and features like rate limiting or geolocation will break entirely.

The Problem with Default Headers

If you just use a basic proxy_pass directive, Nginx strips the original client information. Your Node.js or Laravel application needs to know the original host, the protocol (HTTP vs HTTPS), and the real client IP.

We first tried simply adding proxy_set_header X-Real-IP $remote_addr; to our config, but that wasn't enough. Our Laravel app still didn't trust the proxy, so it kept generating insecure links. We had to explicitly tell Nginx to pass the full chain.

Configuring Nginx Proxy Pass Headers

To get this right, you need to set a specific set of headers in your location block. This ensures that your upstream application receives the context it needs to function correctly.

NGINX
location / {
    proxy_pass http://backend_cluster;
    
    # Essential headers for proxy awareness
    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;
    
    # Timeouts for better stability
    proxy_connect_timeout 60s;
    proxy_send_timeout 60s;
    proxy_read_timeout 60s;
}

By using $proxy_add_x_forwarded_for, Nginx appends the client's IP to the existing X-Forwarded-For list. This is safer than overwriting the header, especially if you have multiple layers of proxies.

Ensuring Upstream Trust

Even with the correct nginx proxy_pass headers, your application framework might ignore them. In Laravel, you must configure the TrustProxies middleware. If you don't, the framework will still see the Nginx internal IP as the "client" IP, which is a major security risk if you're preventing Host Header Injection in Node.js and PHP apps.

HeaderPurposeBackend Use Case
HostOriginal domain nameRouting & URL generation
X-Real-IPThe direct client IPLogging & Analytics
X-Forwarded-ForFull chain of proxy IPsSecurity & Auditing
X-Forwarded-ProtoOriginal request schemeSSL termination handling

Refining Your Nginx Upstream Configuration

When dealing with multiple backend nodes, your nginx upstream configuration should be clean and load-balanced properly. Avoid hardcoding IPs if you can; use DNS names or internal service discovery.

NGINX
upstream backend_cluster {
    # Using least_conn for better distribution
    least_conn;
    server 10.0.0.1:3000;
    server 10.0.0.2:3000;
    keepalive 32;
}

If you are just starting out, I highly recommend reviewing Nginx as a reverse proxy: The config explained line by line to ensure you aren't missing base-level security headers like X-Frame-Options or X-Content-Type-Options.

Common Pitfalls

  1. Forgetting X-Forwarded-Proto: If your Nginx terminates SSL, your Node.js app will think it's on port 80. This breaks redirects.
  2. Missing TrustProxies: Even if Nginx sends the headers, frameworks like Laravel won't use them unless the proxy IP is explicitly whitelisted in the middleware.
  3. Hardcoded Timeouts: Don't rely on default values. If your Node.js app performs long-running tasks, your nginx reverse proxy might close the connection prematurely.

I'm still tinkering with whether it's better to use proxy_buffering off for real-time Node.js applications. It adds overhead, but it prevents the buffer from filling up during large data streams. Next time, I'll probably set up a test environment to measure the latency impact of these buffer settings before pushing to production.

FAQ

Why does my Laravel app still see the Nginx IP? You need to configure the TrustProxies middleware in Laravel to trust the IP of your Nginx server. Without this, Laravel ignores the X-Forwarded-* headers for security reasons.

What is the difference between X-Real-IP and X-Forwarded-For? X-Real-IP typically holds the single IP of the client that connected to the proxy. X-Forwarded-For is a comma-separated list that tracks the entire path of the request through multiple proxies.

Is it safe to pass the Host header? Yes, but you must ensure your backend application validates the Host header against an allowed list to prevent host header injection attacks.

Similar Posts