The questions we're asked most often about logging the real client IP address in Microsoft IIS behind a proxy, load balancer or CDN. If yours isn't here, ask us. We'd rather answer it directly than have you work it out from a forum thread.
There are two approaches, and the right one depends entirely on what reads your logs.
On IIS 8.5 and later you can add X-Forwarded-For as a custom W3C logging field. It's built in, it costs nothing, and if you only need a human to be able to read the address out of a log file, it's sufficient. Stop there.
The catch is that it adds a new column at the end of the line. The standard c-ip field still shows your load balancer or CDN. If anything automated consumes those logs (a SIEM connector, a geo-IP lookup, a log shipper, a compliance parser), it is almost certainly keyed to c-ip, and it will carry on reporting the proxy. Native custom logging also has no trust list, so whatever the header contains is written down, including a value a client made up. There is a second trap worth knowing: once custom fields are configured, IIS writes log files with _x appended to the filename, so a collector matching the old pattern can quietly stop picking anything up.
If you need the real address in c-ip itself, so existing tooling works unchanged, that needs a filter running inside IIS. That's the gap we built X-Forwarded-For for IIS to fill: it reads the header, validates the chain against a Proxy Trust List and writes the genuine client address into the standard field. If you're not sure which header your proxy is even sending, our reference table covers the major vendors.
It won't, and there's no newer build to move to. F5 published two IIS components on DevCentral, an older ISAPI filter and the newer F5XFFHttpModule HTTP module for IIS 7, both community tools rather than supported F5 products. F5 put the source on GitHub in 2015 and archived it in May 2016, before Windows Server 2016 and IIS 10 were generally available, and it hasn't changed since. Microsoft's guidance is that neither works on IIS 10.
Worth knowing even if you can get one running: neither validates the forwarding chain. The published source has no trust list, so whatever arrives in the header is written into c-ip as fact, and anything able to reach IIS directly can choose what your logs say. That is the reason to replace it rather than revive it.
Your BIG-IP configuration isn't affected. Insert X-Forwarded-For on the HTTP profile is fully supported and stays exactly as it is. That's the half that inserts the header. What needs replacing is the IIS-side component that reads it.
The native custom log field is the free option, with the caveat above: it won't replace c-ip, which is usually the reason the filter was deployed in the first place. We make a supported equivalent that does replace it, built and tested for Windows Server 2016–2025, and adds the trust-list validation the original didn't have. Because both write to the same field, migrating changes nothing downstream: see the migration guide for the steps, or the F5 BIG-IP guide for the full configuration.
Yes, for logging. When Exchange is published through a non-transparent load balancer (an F5, a NetScaler, a Kemp, an Application Gateway), the IIS instances hosting OWA, ActiveSync and the other virtual directories see the connection coming from the balancer, so c-ip records that address for every session. The fix is the same shape as any other proxy deployment: have the load balancer insert X-Forwarded-For, then run a filter on the Exchange IIS servers that validates the chain and writes the real address into c-ip.
Note that's a separate problem from IP Address and Domain Restrictions not behaving behind the balancer: IIS has had a free, built-in fix for that one since IIS 8.0 (Enable Proxy Mode). The full Exchange guide covers both: the logging fix and the free native one, including the exact IIS Manager and appcmd steps, per-virtual-directory scoping (OWA, ActiveSync, ECP, EWS, Autodiscover), and the Cumulative Update gotcha that resets it.
c-ip is the W3C log field that records the client's IP address: the "who connected" column in C:\inetpub\logs\LogFiles\. It is one of the fields IIS logs by default, and it is the field essentially every downstream tool reads when it wants to know where a request came from.
The important detail is where the value comes from. IIS fills c-ip from the TCP connection it accepted, not from anything in the request. When a proxy, load balancer or CDN sits in front, the connection IIS accepted came from that device, so c-ip is the proxy's address on every single request, and it stays that way no matter what headers the proxy adds. That is why adding X-Forwarded-For as an extra custom column does not silence the complaints from whatever is parsing your logs: the extra column is new, and c-ip is the one being read.
Yes, always, into the c-ip field: IIS logs the address of whatever made the TCP connection, on every request, with no configuration needed. The catch is "incoming" means the direct connection, not necessarily the visitor. Behind a proxy, load balancer or CDN, the connection IIS accepted came from that device, so c-ip is the proxy's address, not the client's, on every single line.
Not on its own. IIS has no native option to read X-Forwarded-For and write the value into c-ip. Since IIS 8.5 you can log the header as an extra custom W3C field, which is logging it, not parsing it: IIS writes down whatever string arrives, chain and all, with no validation and no effect on c-ip. Actually replacing c-ip with a validated address from the header needs a filter running inside IIS, which is the gap X-Forwarded-For for IIS fills.
X-Forwarded-For, in almost every case. It is the one header essentially every proxy sets, so logging it means one configuration that survives a change of CDN or load balancer. It also carries the whole proxy chain rather than a single address, which is what makes it possible to validate the value against a list of trusted hops instead of believing whatever arrives.
The vendor-specific headers are easier to parse, because each holds exactly one address with no chain to walk: CF-Connecting-IP from Cloudflare, True-Client-IP from Akamai and Cloudflare Enterprise, X-Azure-ClientIP from Azure Front Door. The trade is that they tie your logging to that vendor and give you nothing to validate against. They are a reasonable choice when you are certain traffic can only reach IIS through that one provider. Our reference table lists what each vendor sends.
Yes, trivially. It is just a request header, so any client can send one containing any address it likes. If IIS can be reached directly, or if you log the header without checking where it came from, an attacker can write whatever they want into your audit trail, and your geo-IP reporting and rate limiting will believe it too.
What makes the value trustworthy is not the header, it is knowing which hops you trust. A trust list of your proxy addresses lets the chain be walked from the server outwards, discarding entries contributed by trusted hops and taking the first address that no trusted hop could have forged. Anything that logs the header without that step, including the native IIS custom log field, records the claim rather than the fact. It is also worth restricting IIS at the firewall so it only accepts connections from your proxies in the first place.
As a single comma-separated string: each hop appends the address it saw to the end of whatever it received, so a request that passed through two proxies before reaching IIS arrives as client, proxy1, proxy2. If you log the header as a native custom field, that whole string lands in one column as-is; nothing in IIS splits it for you.
The leftmost entry is the original client only if every hop in the chain is trustworthy, which an unvalidated log can't tell you. The safe read is from the right: walk backwards from the connecting address, drop each entry you recognise as your own infrastructure, and treat the first address you don't recognise as the client. That's the trust-list logic described above, and it's what actually needs doing with a multi-hop chain rather than just picking a position in the list.
Confirm it arrives before worrying about whether the value is correct. Turn on the native custom W3C logging field for X-Forwarded-For (see the first question above), make a real request through the proxy, and check the new log column: if it's populated at all, your proxy is sending the header. If it's empty, the problem is upstream. Fix that first, whichever load balancer, CDN or reverse proxy config you're on. Our reference table covers what each major vendor sends and what has to be switched on.
Once the header is confirmed arriving, the second thing worth checking is whether it's trustworthy, not just present: hit the site directly (bypassing the proxy, if the origin is reachable) and add your own X-Forwarded-For header with curl or a browser extension. If IIS or your logging accepts that value uncritically, the header is currently unvalidated, which is the case for the native custom log field and for the old, discontinued F5 ISAPI filter alike.
No, and this catches people out regularly. The forwarded-headers middleware rewrites what your application sees, so HttpContext.Connection.RemoteIpAddress, your app's own logging and your authorisation policies all get the real client. IIS writes its W3C log entry from the connection it accepted, before your application runs and independently of anything the middleware later concludes. So you can finish configuring ForwardedHeadersOptions correctly, watch your application log the right address, and still find the proxy's IP in c-ip on every line. Correcting the log itself needs something running inside IIS at the point the field is written. The ASP.NET Core guide covers the middleware in detail, including the KnownProxies default that makes it work locally and silently do nothing in production.
The vendor guides go into far more detail for specific setups (Cloudflare, F5 BIG-IP, AWS, Azure, ARR and Nginx or HAProxy), and each ends with its own FAQ. There's also dedicated guides for Exchange behind a load balancer and SharePoint behind a reverse proxy, a page on what this means for SIEM and compliance, and one on the ASP.NET Core middleware for anyone weighing that up instead.
If your setup isn't covered here, tell us what's in front of IIS and what needs the address, and we'll tell you whether we can help, including when the answer is that you don't need us.
Get in touch or read about the product →