Logging the real client IP in IIS behind Nginx or HAProxy

Unlike a cloud load balancer, Nginx and HAProxy forward nothing about the original client unless you configure them to. Here's the one directive each needs, and how to get the real address into your IIS logs once it's arriving.

Short answer. Neither proxy sends X-Forwarded-For by default. In Nginx add proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; to the location block; in HAProxy add option forwardfor to the backend. That gets the visitor's address as far as IIS, which still won't log it, because IIS doesn't read the header. Install an ISAPI filter that writes it into the standard c-ip field, trusting only your proxy's addresses.

The problem

Nginx and HAProxy both terminate the client connection and open a new one to your IIS backend. As far as IIS is concerned every request originates from the proxy, so the c-ip field in your logs shows the same one or two addresses for all traffic, and geolocation, reporting, rate-limiting and security tooling lose sight of the real visitor.

What makes these two different from Cloudflare or an AWS load balancer is that they add nothing on your behalf. A managed service inserts X-Forwarded-For automatically; Nginx and HAProxy are yours to configure, and out of the box they forward the request without any record of who sent it. If the header never gets added upstream, no amount of work on the IIS side can recover the address.

Step 1: make the proxy send the header

Nginx

In the location block that proxies to IIS:

location / {
    proxy_pass         http://iis_backend;
    proxy_set_header   Host              $host;
    proxy_set_header   X-Forwarded-For   $proxy_add_x_forwarded_for;
    proxy_set_header   X-Forwarded-Proto $scheme;
}

The variable that matters is $proxy_add_x_forwarded_for. It appends the connecting address to any X-Forwarded-For header already present, rather than replacing it, which is what preserves the chain when Nginx sits behind another proxy or a CDN. Setting the header to $remote_addr instead would discard everything upstream of Nginx.

Reload with nginx -s reload after testing the config with nginx -t.

HAProxy

In the backend serving your IIS servers:

backend iis_servers
    option forwardfor
    server web1 10.0.0.11:80 check
    server web2 10.0.0.12:80 check

option forwardfor adds the client's address to X-Forwarded-For. Placing it in the defaults section applies it to every backend at once. If HAProxy itself sits behind another proxy, add except with that proxy's address so HAProxy doesn't record the intermediate hop as the client.

Step 2: make IIS log it

The header now reaches your web servers, but IIS doesn't read X-Forwarded-For natively. Microsoft never built it in, so the header arrives and is discarded, and c-ip still shows the proxy. That's the gap Winfrasoft X-Forwarded-For for IIS fills.

It's an ISAPI web filter that reads the header and writes the real client address into the standard IIS c-ip log field. Nothing changes in your applications or log format, so existing SIEM and log-analysis tooling keeps working unchanged. A Proxy Trust List of your Nginx or HAProxy addresses ensures only that hop is trusted, so the genuine visitor is logged and the header can't be spoofed.

Step by step

  1. Configure the proxy as above, and confirm the header is arriving: a quick way is to log $http_x_forwarded_for at the proxy, or capture a request at the backend.
  2. Install X-Forwarded-For for IIS on each backend web server (IIS 10 on Windows Server 2016–2025).
  3. Add the proxy's addresses to the Proxy Trust List. For HAProxy this is the address the backend connections come from; for Nginx, the server's outbound address. (See the Installation & Configuration Guide for the exact trust-list format.)
  4. Run IISRESET, then browse the site and confirm the IIS c-ip field shows real visitor addresses.

Lock down the backend. Because you control this proxy rather than renting it, the origin is often reachable on the same network, which means anyone who can reach IIS directly can forge an X-Forwarded-For header. Restrict the backend firewall or IIS IP restrictions to accept web traffic only from your proxy, so the header can only arrive through it.

Frequently asked questions

Does Nginx send X-Forwarded-For by default?

No. Nginx forwards the request without adding any client-IP header unless you configure proxy_set_header yourself. This is the most common reason a proxied IIS site logs only the Nginx address — the header was never sent in the first place.

What's the difference between $proxy_add_x_forwarded_for and $remote_addr?

$remote_addr is just the address Nginx received the connection from. $proxy_add_x_forwarded_for is that address appended to any existing X-Forwarded-For header, preserving the chain when there's a CDN or another proxy in front. Use the latter unless you have a specific reason not to.

Should I use X-Real-IP instead?

X-Real-IP is a common Nginx convention holding a single address. It's simpler to parse but carries no proxy chain, so it can't be validated against a trust list, and it isn't a cross-vendor standard. X-Forwarded-For is the better choice for logging. See which header each proxy sends.

Where does option forwardfor go in HAProxy?

In a backend section to apply it to that backend, or in defaults to apply it to all of them. It takes effect for HTTP-mode proxying; in TCP mode HAProxy isn't parsing HTTP at all, so use the PROXY protocol instead.

The header is arriving but IIS still logs the proxy. Why?

Because IIS doesn't read it. The c-ip field records the address of whatever opened the TCP connection, and nothing in IIS inspects X-Forwarded-For. Getting the real address into the log requires a filter that reads the header and writes it there.

Get X-Forwarded-For for IIS

Restore real visitor IPs in your IIS logs behind Nginx or HAProxy. Available now for IIS 10 on Windows Server 2016–2025, with a free 14-day trial.

View the product   or request a download →