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.
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.
NGINXlocation / { 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.
| Header | Purpose | Backend Use Case |
|---|---|---|
Host | Original domain name | Routing & URL generation |
X-Real-IP | The direct client IP | Logging & Analytics |
X-Forwarded-For | Full chain of proxy IPs | Security & Auditing |
X-Forwarded-Proto | Original request scheme | SSL 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.
NGINXupstream 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
- Forgetting
X-Forwarded-Proto: If your Nginx terminates SSL, your Node.js app will think it's on port 80. This breaks redirects. - 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. - 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.