IIS custom field for X-Forwarded-For not working

You followed the steps, added the field, clicked OK, and the column is missing, empty, or every row is a dash. Nine things cause this. The first one catches almost everyone, and it is not a mistake you made.

Short answer. The single most common cause is that you are reading the wrong file. Microsoft documents that once custom fields are configured, "IIS will create new text log files with _x appended to the file name". Your old u_ex260907.log keeps being written without the column, and the data you are looking for is in u_ex260907_x.log next to it. The rest are a greyed-out Add Field button, a lower-case server variable name, the wrong Source Type, a non-W3C log format, the 60 second log buffer, IIS older than 8.5, per-field truncation, and forgetting to click Apply. And when it does work, it still leaves c-ip showing your proxy.

1. The log file name changed and nobody told you

This is the one to check before anything else, because the configuration is usually correct and the file is simply somewhere else.

Microsoft's Enhanced Logging documentation states it plainly: "Once the custom fields have been configured, IIS will create new text log files with _x appended to the file name to indicate that the file contains custom fields."

So in C:\inetpub\logs\LogFiles\W3SVC1\ you will find both:

u_ex260907.log      <- the old schema, no custom column
u_ex260907_x.log    <- your custom fields are in here

Open the _x file and check the #Fields: header line at the top. Your field name should be the last entry.

This has a second consequence that is easy to miss and expensive to discover later: anything that collects your logs by matching a filename pattern will silently stop seeing new data, or will keep reading the old file and never see the new column. A SIEM collector watching u_ex*.log may or may not match u_ex*_x.log depending on how the pattern is written. Check the collector before you close the ticket.

2. Add Field is greyed out

If the Add Field button and the Custom Fields section of the W3C Logging Fields dialog are disabled, you have the server selected in the Connections pane rather than a site.

Microsoft's documentation repeats this twice in the same procedure: "enhanced logging is available only for site-level logging". Select the individual website and both become available.

That is the IIS Manager UI, not the whole story. Microsoft's configuration reference documents a <customFields> collection under siteDefaults in applicationHost.config, and says that element "is configured at the server level". What it sets are the defaults new sites inherit, which is not the same as switching the feature on everywhere at once. Existing sites keep their own configuration.

So on a farm you have two practical options, and clicking through IIS Manager is neither of them.

Per site, which is the reliable one:

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

Or as a default for sites created later:

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'}

One caution on the server-level route. A Microsoft Q&A thread reports that with a custom field set at server level and 100 or more sites on the server, the field is not written as a header at all: it appears mangled into the data of each entry, and the _x suffix never shows up on the filename. Below 100 sites the same configuration behaves correctly. Microsoft did not confirm this as a known issue in the thread, and asked the reporter to open a support case, so treat it as a field report rather than documented behaviour. The workaround given there is the one above: apply the field per site instead.

The same symptom shows up independently in an earlier thread on IIS 8.5, where the field name and value appear mangled into the position s-sitename should occupy, on about ten servers out of an estate that was otherwise identically configured. Microsoft staff there acknowledged it may be a bug and asked for a support case. Two unresolved reports of the same corruption is enough to recognise the symptom, and not enough to say what triggers it, so if your log looks like that the per-site route is the thing to try rather than a configuration you can debug.

Either way, any site created after you finish starts without the field unless siteDefaults carries it, so put it into whatever provisions new sites.

3. The column exists but every row is a dash

A - in a W3C log means the value was not present for that request. Work through these in order:

Check How
Is the header actually arriving? Most "not working" reports turn out to be a proxy that was never configured to send it. Hit the site through the proxy and log every request header temporarily, or use Failed Request Tracing. If the header is absent at IIS, the problem is upstream, not in IIS. See which header does your proxy send, because several vendors do not send X-Forwarded-For as the primary header.
Are you testing from the right place? A request you make directly to the IIS server, bypassing the proxy, will never carry the header. Test through the public hostname, not localhost and not the server's own address.
Is the Source string right for the Source Type you chose? See cause 4 below. This is the most common configuration error.
Is the proxy stripping it? Some WAFs remove inbound forwarding headers by design and then re-add their own. Check the vendor's client IP setting. The vendor guides linked at the foot of this page cover the relevant toggle for each.

4. Request Header and Server Variable are not interchangeable

The Add Custom Field dialog asks for a Source Type and a Source, and the two have to match. There are two valid ways to capture the same header, and a very common invalid combination:

Source Type Source Result
Request Header X-Forwarded-For Works. This is the form Microsoft's own example uses.
Server Variable HTTP_X_FORWARDED_FOR Works, in upper case only.
Server Variable http_x_forwarded_for Silently logs nothing. Microsoft documents that "enhanced logging cannot log a server variable with a name that contains lower-case characters".
Request Header HTTP_X_FORWARDED_FOR Logs nothing. There is no request header by that name. HTTP_ is the CGI server-variable prefix, not part of the header.

If you copied the name out of a code sample or an old ASP page, you almost certainly have the HTTP_ prefixed form, and pairing it with Source Type Request Header is the single most common way to get a column full of dashes. There is no error and no warning: it just never matches.

Microsoft's own two pages disagree on the Source string, which does not help. The Enhanced Logging walkthrough says "to record the custom HTTP Header X-FORWARDED-FOR, enter that string in Source", with hyphens. The <customFields> configuration reference then gives a sample using sourceName="X_FORWARDED_FOR" with underscores, against sourceType="RequestHeader". The name on the wire is hyphenated, so that is the form to use. If you inherited a configuration carrying the underscored variant, that is worth testing before you look anywhere else.

One more constraint from the same page: the Field Name cannot contain spaces. That is the name of your new column, not the Source.

5. The log format is not W3C

Custom fields only exist under the W3C format. If Format under Log File is set to IIS, NCSA or Custom, the Select Fields button that leads to the whole dialog is not offering you custom fields at all. Set Format to W3C first, then Select Fields, then Add Field.

6. You are looking too soon

IIS log writes are buffered by HTTP.sys for performance. By default entries are flushed to disk roughly every 60 seconds, or when the buffer reaches 64 KB, whichever comes first. A request you made ten seconds ago is very often simply not on disk yet.

To force a flush from an elevated command prompt:

netsh http flush logbuffer

Reports of this command flushing only the default site do circulate, so if it returns Ok and you still see nothing, wait out the full minute and generate a few more requests before concluding the configuration is wrong.

7. IIS is older than 8.5

Enhanced Logging was introduced in IIS 8.5, which means Windows Server 2012 R2. Microsoft's compatibility table is explicit that it was not supported before then. On IIS 7, 7.5 or 8.0 the Custom Fields section does not exist, and the forum answers that tell you to install the Advanced Logging extension instead are pointing at a dead end: Microsoft has discontinued that extension and the download is gone. That story is covered in full in the Advanced Logging route is gone.

8. You clicked OK but not Apply

The Add Custom Field and W3C Logging Fields dialogs each have an OK button, and dismissing both still leaves the change pending. The configuration is not written until you click Apply in the Actions pane of the Logging feature. It is a small thing, and it is in Microsoft's procedure as its own numbered step for a reason.

9. The value is there but cut short

Two separate limits apply, and the smaller one is the one that bites.

Microsoft documents a per-field cap, maxCustomFieldLength: "The maximum amount of data, in bytes, that can be added to a log file in any one custom field. The range is 2 to 65,536. The default value is 4096." There is also a total across all custom fields of 65,536 bytes, beyond which IIS truncates.

For a normal X-Forwarded-For chain, 4096 bytes is far more room than you need, so this is rarely the cause on its own. It matters if somebody has lowered maxCustomFieldLength, or if you are logging several large headers alongside it. If a value looks like it stops mid-address, check the attribute before assuming the proxy sent a truncated header.

When it is working and still does not solve your problem

This is worth saying plainly, because a large share of people who reach this page have a working custom field and an unsolved problem.

A custom field adds an extra column. It does not change c-ip. The c-ip field is filled from the TCP connection, and behind a proxy the only connection IIS sees is the proxy's, so c-ip keeps showing the proxy no matter how many custom fields you add. Everything downstream that already reads c-ip, which is most SIEM content, geo-IP reporting, rate limiting and audit tooling, carries on seeing the wrong address.

The new column also contains the raw header exactly as it arrived, including the entire chain and anything a caller chose to forge into it. Microsoft notes the data collected across all custom fields is capped at 65,536 bytes and truncated beyond that, with a separate per-field default of 4,096. So you now own the trust problem: something has to decide which address in that string is real.

What a multi-proxy chain looks like in the column

With more than one hop in front of IIS, the column holds a comma-separated list that grows left to right as each proxy appends the address it received the connection from:

#Fields: date time s-ip cs-method cs-uri-stem c-ip sc-status X-Forwarded-For
2026-09-07 09:14:02 10.0.0.20 GET /index.html 10.0.0.9 200 203.0.113.7,+198.51.100.24,+192.0.2.60

Three things to notice. c-ip is 10.0.0.9, your inner proxy, unchanged. The spaces in the header are written as + because W3C logs are space-delimited. And the leftmost address, 203.0.113.7, is the one you must not trust: it is the furthest from you and the only one a caller could have supplied before your infrastructure ever saw the request. The real client is found by reading from the right and stopping at the first address that is not a proxy you control. The header reference works through the ordering in detail.

The fix: X-Forwarded-For for IIS

Winfrasoft X-Forwarded-For for IIS is an ISAPI web filter that reads the header and writes the real client address into the standard IIS c-ip field. No new column, no _x file, no change to the W3C schema your collector already parses, and no per-site custom field to remember on the next site you create. A Proxy Trust List holds your own proxy addresses, so the filter walks the chain from the right, skips the hops you trust, and logs the first address you did not, which is what makes the value dependable rather than merely present.

Frequently asked questions

Why is my IIS custom log field for X-Forwarded-For empty?

Either you are reading the pre-change log file rather than the new _x one, or the header is not reaching IIS, or the Source string does not match the Source Type you chose. A dash in a W3C log means the value was absent for that request, so the fastest test is to confirm the header is arriving at all before touching the logging configuration again.

Where did my IIS log file go after adding a custom field?

Nowhere. IIS started writing a second file alongside it. Microsoft documents that once custom fields are configured, IIS creates new text log files with _x appended to the file name, so u_ex260907.log is joined by u_ex260907_x.log and the custom column is only in the latter. Anything collecting logs by filename pattern needs to be checked.

Should I use X-Forwarded-For or HTTP_X_FORWARDED_FOR as the source?

It depends on the Source Type. With Source Type set to Request Header, use the header name X-Forwarded-For. With Source Type set to Server Variable, use HTTP_X_FORWARDED_FOR in upper case, because Microsoft documents that enhanced logging cannot log a server variable whose name contains lower-case characters. Pairing the HTTP_ prefixed form with Request Header matches nothing and logs a dash on every request.

Why is Add Field greyed out in IIS Manager?

Because the server is selected in the Connections pane rather than a site. Enhanced logging is available only for site-level logging, so both the Custom Fields section and the Add Field button are disabled at server level. Select the individual website and they become available. The UI is not the only route: a <customFields> collection exists under siteDefaults in applicationHost.config and can be set with PowerShell, but it supplies defaults to sites created afterwards rather than switching the feature on for existing ones.

How long before entries appear in the IIS log?

Up to about a minute. HTTP.sys buffers log writes and flushes them roughly every 60 seconds or when the buffer reaches 64 KB, whichever happens first. Run netsh http flush logbuffer from an elevated prompt to force a write, then re-check the _x file.

My custom field works. Why does c-ip still show the load balancer?

Because a custom field adds a column and changes nothing else. c-ip is populated from the TCP connection, and the only connection IIS sees is the one your proxy opened, so it records the proxy regardless of how many headers you log. Getting the real address into c-ip itself needs something that rewrites that field, which is what X-Forwarded-For for IIS does.

Which address do I take when the column has several?

Not the leftmost, despite that being the usual advice. Each proxy appends the address it received the connection from, so the list grows left to right and the leftmost entry is whatever the caller sent before your own infrastructure touched the request. Read from the right, skip the addresses of proxies you control, and the first address that is not yours is the client.

Get X-Forwarded-For for IIS

Put the real client address in the standard IIS c-ip field instead of a second log file and a column your tooling does not read. IIS 10 on Windows Server 2016–2025, with a free 14-day trial.

View the product   or request a download →