How to log X-Forwarded-For in IIS

IIS does not log the X-Forwarded-For header by default, so behind a proxy every line of your log shows the same address. You can add the header as a custom column in a couple of minutes. Whether that actually solves your problem depends on what reads the log afterwards, which is the part most guides leave out.

Short answer. On IIS 8.5 and later, open Logging in IIS Manager, choose Select Fields, then Add Field, and add X-Forwarded-For as a Request Header source. That appends a cs(X-Forwarded-For) column to the log. It does not change c-ip, which is the field most log tooling actually reads. To make c-ip itself show the real client, you need an ISAPI filter that validates the header, such as X-Forwarded-For for IIS.

How do I log X-Forwarded-For in IIS?

Pick the method that matches your IIS version. All three add a column; none of them alter c-ip.

IIS 8.5 and later (Windows Server 2012 R2 onwards)

  1. Open IIS Manager and select the site in the Connections pane. It has to be the site: Microsoft documents that enhanced logging is available only for site-level logging, so with the server selected the Custom Fields section and Add Field are disabled. If those controls look greyed out, that is why. To apply it across every site at once, use the PowerShell method below instead.
  2. Double-click Logging.
  3. Confirm the format is W3C, then click Select Fields….
  4. Click Add Field… and enter: Field Name X-Forwarded-For, Source type Request Header, Source X-Forwarded-For.
  5. Click OK twice, then Apply in the Actions pane.

The change takes effect on the next log file, not the current one. If you want to see it immediately, recycle the application pool so IIS starts a new file.

Watch the filename. Once custom fields are configured, Microsoft documents that IIS "will create new text log files with _x appended to the file name". Anything ingesting logs by filename pattern can silently stop matching, or start reading two sets of files with different schemas. If a SIEM or log shipper goes quiet shortly after you add the column, check this first. There is also a cap: the total data across all custom fields cannot exceed 65,536 bytes, after which IIS truncates it.

IIS 7, 7.5 and 8 (Advanced Logging)

This route is closed on anything current. Advanced Logging was a separate downloadable extension rather than part of IIS, and Microsoft has discontinued it: the download is no longer available. Its readme required IIS 7 on Windows Server 2008, 2008 R2, Vista SP1 or Windows 7, so it will not install on IIS 10 even if you track down a copy on a mirror. The steps below are kept for anyone still running a legacy server that already has it in place. On Windows Server 2016 or later use the IIS 8.5 method above, and see the Advanced Logging dead end for the full picture.

Custom fields were not built into IIS logging until 8.5. On IIS 7 to 8 the Advanced Logging module was the way to do this:

  1. Open IIS Manager and double-click Advanced Logging.
  2. Click Enable Advanced Logging.
  3. Choose Edit Logging Fields, then Add Field: Field ID X-Forwarded-For, Source type Request Header, Source name X-Forwarded-For.
  4. Edit the default log definition, tick the new field, and apply.
  5. Generate some traffic so a new log file is created.

Advanced Logging writes its own files rather than extending the standard W3C log, so anything parsing the original path will not see the new column.

PowerShell, server-wide

To add the field to every site at once, set it on the site defaults in applicationHost.config:

Add-WebConfigurationProperty -pspath 'MACHINE/WEBROOT/APPHOST' `
  -filter "system.applicationHost/sites/siteDefaults/logFile/customFields" `
  -name "." `
  -value @{logFieldName='X-Forwarded-For';sourceName='X-Forwarded-For';sourceType='RequestHeader'}

Run it from an elevated session. Sites that already override their logging configuration keep their own settings, so check those individually.

Reading the new column

Once the column appears, three things are worth knowing before you trust it:

Your proxy has to be sending the header in the first place. Most do not by default: on an F5 BIG-IP it is the Insert X-Forwarded-For setting on the HTTP profile, and other vendors use their own header names entirely. The reference of which client IP header each proxy sends covers what to look for and what to enable.

What does c-ip stand for?

Client IP. The W3C Extended Log File Format that IIS uses names its fields by prefix: c- for client, s- for server, cs- for client-to-server, and sc- for server-to-client. So c-ip is the client's address, s-ip is the server's, cs-method is the method the client sent, and sc-status is the status code the server returned.

Reading the prefix makes the rest of the log self-explanatory. Here are the fields you'll actually meet:

Field What it holds
c-ip Client IP. The address of the TCP peer. Behind a proxy this is the proxy.
s-ip Server IP. Which of the server's own addresses accepted the request.
cs-method The HTTP method: GET, POST and so on.
cs-uri-stem The requested path, without the query string.
cs-uri-query The query string, or - if there wasn't one.
cs-username The authenticated user, or - for anonymous requests.
cs(User-Agent) A request header. Parenthesised names are headers rather than connection facts.
sc-status The HTTP status code returned.
time-taken How long the request took, in milliseconds.

That distinction between a plain name and a parenthesised one matters more than it looks, and it is the root of the confusion this page exists to clear up. c-ip is a fact about the connection. cs(X-Forwarded-For) would be a header the client sent. They are filled from different places and they are not interchangeable.

Where are the IIS logs?

By default, one folder per site under:

C:\inetpub\logs\LogFiles\W3SVC1\

The number after W3SVC is the site ID from IIS Manager, so a server hosting several sites has several folders. Each file opens with a #Fields: line naming the columns in order, which is what tells you (and your log parser) which position c-ip occupies:

#Fields: date time s-ip cs-method cs-uri-stem cs-uri-query
s-port cs-username c-ip cs(User-Agent) cs(Referer) sc-status
sc-substatus sc-win32-status time-taken

If c-ip isn't in that line, the field has been switched off in the site's logging configuration. In IIS Manager that is Logging, then Select Fields.

Why does c-ip show my load balancer's address?

Because a reverse proxy terminates the client's connection and opens its own to IIS. IIS records the peer of the connection it accepted, which is the proxy. The visitor's address never reaches the TCP layer, so c-ip has no way to know it.

The proxy usually does pass the original address along, in the X-Forwarded-For request header. The gap is that IIS does not read it. Microsoft never built X-Forwarded-For handling into IIS logging, so the header arrives, sits in the request, and is discarded unless something reads it and acts on it. Our reference of which header each proxy sends covers what your particular device puts where.

The symptom is unmistakable once you know it: every line in the log carries one of a small set of addresses, matching the number of proxies or SNAT addresses in front of the estate.

How do I check what my c-ip actually contains?

Open the newest log file and look at the column. If you want to be quicker about it, this counts the distinct values so you can see the problem rather than infer it:

$log = Get-ChildItem C:\inetpub\logs\LogFiles\W3SVC1 |
       Sort-Object LastWriteTime | Select-Object -Last 1

$fields = (Select-String '^#Fields:' $log.FullName |
           Select-Object -Last 1).Line -replace '^#Fields: ' -split ' '
$i = [Array]::IndexOf($fields, 'c-ip')

Get-Content $log.FullName |
    Where-Object { $_ -notmatch '^#' } |
    ForEach-Object { ($_ -split ' ')[$i] } |
    Group-Object | Sort-Object Count -Descending |
    Select-Object -First 10 Count, Name

A healthy internet-facing site returns a long tail of distinct addresses. A handful of values covering every request means c-ip is recording your infrastructure.

What is the difference between c-ip and cs(X-Forwarded-For)?

This is the distinction that decides whether the steps at the top of this page actually solve your problem. The custom field is free and native, but it adds a column and leaves c-ip alone, and c-ip is what the rest of your tooling reads.

c-ip cs(X-Forwarded-For)
Filled from The TCP connection A request header
Behind a proxy, shows The proxy Whatever the header says
Position in the log Standard field, expected by parsers Appended at the end, after the standard fields
Validated? Cannot be forged: it is the connection No trust list. A forged header is logged as sent
Read by SIEM connectors and geo-IP tooling Yes, by default Only after reconfiguring each one, where that is even possible

So the custom field answers "what did the proxy claim?" while leaving the field everything else reads still pointing at the proxy. If you own the log analysis end to end and can repoint it, that may be enough. If a SIEM, a compliance requirement or a packaged reporting tool is involved, it usually isn't, because those key off c-ip and many cannot be told otherwise. That trade-off is covered in more depth in why SIEM and compliance tooling needs the c-ip field.

The forgery problem

The custom field records the header exactly as sent, with no trust check of any kind. Anything that can open a connection to IIS can set X-Forwarded-For to any value it likes, and the log will record it faithfully. On a server reachable only through the proxy that is a theoretical concern. On one that is also reachable directly, whether by design, by a firewall gap or by an internal network route, it means the column can be written by the visitor rather than by your proxy.

This is the practical reason the custom field is a diagnostic aid rather than an audit record. It tells you what the proxy claimed. It does not tell you it was the proxy that claimed it.

Getting the real client address into c-ip

The only way to make c-ip itself correct is for something to read the forwarded header and write the address into the field as IIS records it. That is what an ISAPI filter does, and it is why the approach has been in use since the IIS 6 era.

The important part is the trust check. Because a header can be forged, a filter worth using validates the forwarding chain against a list of proxies you trust, and ignores the header when it arrives from anywhere else. Without that, correcting c-ip would trade a wrong-but-honest log for a confidently-wrong one, which is worse for an audit trail than no change at all.

That is what X-Forwarded-For for IIS does. It reads the forwarded header, checks each hop against a Proxy Trust List, and writes the validated client address into the standard c-ip field, so the log keeps its default layout and SIEM connectors, log analysers and geo-IP tooling need no changes. It runs on IIS 10 on Windows Server 2016 to 2025, and you can request a download to try it against your own logs.

Frequently asked questions

How do I log X-Forwarded-For in IIS?

On IIS 8.5 and later, open Logging in IIS Manager, choose Select Fields, then Add Field, and add X-Forwarded-For with a source type of Request Header. On IIS 7 to 8 it needed the separate Advanced Logging module, which Microsoft has since discontinued and no longer offers for download. Either way the header is added as a new column and the existing c-ip field is unchanged.

Does IIS log X-Forwarded-For by default?

No. Microsoft never built X-Forwarded-For handling into IIS logging. The header arrives with the request and is discarded unless you add a custom log field to capture it or install something that reads it. This is why an unmodified IIS server behind a load balancer logs the same address on every line.

Why does the X-Forwarded-For column show a dash?

A dash means no X-Forwarded-For header was present on that request. Either the request did not pass through a proxy, or the proxy is not configured to insert the header. On an F5 BIG-IP, for example, it is off until you enable Insert X-Forwarded-For on the HTTP profile.

What does it mean when X-Forwarded-For contains several IP addresses?

Each proxy in the chain appends the address it received the request from, so a comma-separated list means the request passed through more than one hop. The left-most entry is the originating client. It is also the entry a client can set itself, so it is the least trustworthy part of the value.

Can I make IIS log the real client IP in c-ip instead of adding a column?

Not with native IIS configuration. The custom log field can only add a column. Writing the real address into c-ip itself requires an ISAPI filter or native module that reads the forwarded header and rewrites the field before IIS logs the request, validating the forwarding chain against proxies you trust.

What is c-ip in IIS logs?

c-ip is the client IP address field in the IIS W3C log. IIS fills it with the address of whatever opened the TCP connection to the server. On a directly-exposed server that is the visitor; behind a proxy, load balancer or CDN it is that device instead.

Why is c-ip the same address on every request?

Because a reverse proxy is terminating the client connection and opening its own to IIS, so every request genuinely arrives from the proxy. The number of distinct addresses you see usually matches the number of proxies or SNAT addresses in front of the servers.

Does IIS use X-Forwarded-For for c-ip automatically?

No. Microsoft never built X-Forwarded-For support into IIS logging. The header arrives with the request and is discarded unless a filter or module reads it. c-ip always records the TCP peer address unless something explicitly rewrites it.

What is the difference between c-ip and cs(X-Forwarded-For)?

c-ip is a standard field filled from the connection and cannot be forged. cs(X-Forwarded-For) is an optional custom field filled from a request header, appended after the standard fields, and logged exactly as sent with no trust validation. Adding the custom field does not change c-ip.

Where is the c-ip field in the IIS log file?

Its position varies by configuration, which is why each log file starts with a #Fields: line naming the columns in order. Read that line rather than assuming a fixed column number, because a server with custom fields enabled will not match the default layout.

Can I turn the c-ip field off?

Yes, in IIS Manager under Logging, then Select Fields, though there is rarely a reason to. If c-ip is missing from a log's #Fields: line, that is what has happened. Most log tooling treats its absence as a broken log rather than an empty one.

Make c-ip show the real visitor

X-Forwarded-For for IIS reads the header your proxy sets and writes the real client address into the standard c-ip field, validated against a Proxy Trust List so a forged header is ignored. Nothing downstream needs reconfiguring. IIS 10 on Windows Server 2016–2025.

View the product   or request a download →