Lesson 7 / 25

proxy_pass and Forwarded Headers

Proxy requests to an application and pass the client information it needs.

NGINX in front of your app

As a reverse proxy, NGINX accepts client connections and forwards requests to upstream servers with proxy_pass, returning their responses. This lets NGINX handle TLS, compression, buffering of slow clients, static files and rate limits, while the application focuses on logic. By default, NGINX sends the upstream a Host header equal to the proxied host name and the connection comes from NGINX's own IP, so the application loses track of the original request. Pass the important information explicitly: Host $host (the original host name), X-Real-IP $remote_addr, X-Forwarded-For $proxy_add_x_forwarded_for (appends the client IP to any existing chain), and X-Forwarded-Proto $scheme (so the app knows the original request was HTTPS, important for redirects and secure cookies). Configure the application to trust these headers only from the proxy's address. The standard Forwarded header (RFC 7239) is an alternative some frameworks support.

Client, proxy and upstream

NGINX terminates the client connection and opens its own connection to the application, forwarding key details as headers.

A client icon connected to a middle proxy box, which connects to two application boxes; small header tags travel along the second arrow.
Figure 3.1 — A reverse proxy passing forwarded headers upstream.

A reverse proxy for an application

Forwarded headers tell the app who the real client was and which scheme they used.

upstream app_backend {
    server 127.0.0.1:3000;
    keepalive 32;                         # reuse connections to the app
}

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

    location / {
        proxy_pass http://app_backend;
        proxy_http_version 1.1;
        proxy_set_header Connection "";      # required for upstream keepalive
        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;
    }
}

Behind a load balancer, restore the real client IP

If NGINX itself sits behind a cloud load balancer, $remote_addr is the load balancer. Use the real_ip module (set_real_ip_from <lb-range>; real_ip_header X-Forwarded-For;) so logs and rate limits see real clients, trusting only known proxy ranges.

Quick check: Why set `proxy_set_header X-Forwarded-Proto $scheme`?

  • So the application knows whether the client used HTTP or HTTPS
  • To compress responses
  • To enable HTTP/2
  • To cache responses
Answer

So the application knows whether the client used HTTP or HTTPS — The app sees only NGINX's connection, so the original scheme must be forwarded explicitly.