# Real client IP, path base and Host through a reverse proxy

> Forward X-Forwarded-For/Proto/Prefix and Host correctly, trust only the proxy, and stop redirect loops and wrong absolute URLs.

**Tested on**

- Build: 26100.33650
- Edition: Standard / Datacenter
- Architecture: x64
- Evidence: E1
- Verified: 2026-10-29

## The short answer

A reverse proxy must tell the app:

- **who the client is**: `X-Forwarded-For` (or RFC 7239 `Forwarded`);
- **which scheme**: `X-Forwarded-Proto`;
- **which host**: preserve `Host`;
- **which path prefix**: `X-Forwarded-Prefix` when the app is mounted under `/app`.

And the app must trust these headers **only** from the proxy.

## Nginx

```nginx
location / {
    proxy_pass http://127.0.0.1:3000;
    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_set_header X-Forwarded-Host $host;
}
```

For a path-mounted app:

```nginx
location /app/ {
    proxy_pass http://127.0.0.1:3000/;
    proxy_set_header X-Forwarded-Prefix /app;
}
```

The trailing slash on `proxy_pass` strips `/app/`; the prefix header tells the app how to generate links.

## IIS ARR

- Preserve `Host` in the farm/proxy settings.
- Forward `X-Forwarded-For`/`X-Forwarded-Proto`.
- If the app is under a path, set `X-Forwarded-Prefix` via URL Rewrite server variables.
- Do not rewrite `Host` to `localhost` unless the app is configured for it.

## App trust

Express:

```js
app.set('trust proxy', 1);
```

ASP.NET Core:

```csharp
app.UseForwardedHeaders(new ForwardedHeadersOptions {
    ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
});
```

Configure known proxies/networks; the default often trusts only loopback. Do not trust all proxies on a public host.

## Redirect loops

Symptom: browser bounces between `http` and `https`, or redirects to `localhost`.

Causes:

- proxy sends `X-Forwarded-Proto: http` after TLS termination;
- app ignores the header and redirects to HTTPS;
- proxy rewrites `Host` to an internal name;
- canonical-host rule catches the health check.

Fix the header/trust boundary, not by disabling HTTPS redirects globally.

## Verify

```powershell
curl.exe -I -H "X-Forwarded-Proto: https" https://lab.example/
curl.exe -H "Host: app.example" http://127.0.0.1:3000/whoami
```

The app should report the public scheme/host and the real client IP, and redirects should use the public name.

> **Caution: Forwarded headers are spoofable**
>
> If clients can reach the app directly, they can send `X-Forwarded-For` themselves. Bind the app to localhost/private and only trust the proxy’s address.

## Lab boundary

E1. The lab ran Nginx/IIS without a multi-hop proxy; the headers are the configuration to validate with a real upstream on Server 2025.

## Sources

- [nginx — proxy headers](https://nginx.org/en/docs/http/ngx_http_proxy_module.html)
- [Microsoft Learn — ARR reverse proxy](https://learn.microsoft.com/en-us/iis/extensions/url-rewrite-module/reverse-proxy-with-url-rewrite-v2-and-application-request-routing)
- [RFC 7239 — Forwarded HTTP extension](https://datatracker.ietf.org/doc/html/rfc7239)

---

Source: https://windows.homes/en/server/tls/proxy-headers/
