# proxy_pass and Forwarded Headers — Nginx

Source: https://www.skillbyai.com/en/nginx/p-basics

> 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.](assets/figures/nginx/section-3-map.svg) — 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.

```nginx
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.

**Quiz:** Why set `proxy_set_header X-Forwarded-Proto $scheme`?

- [x] 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.
