APM Tips

Application Insights

Client IP address and geolocation in Application Insights

Updated · 6 min read · By APM Tips editorial team

By default Application Insights uses the client's IP address once, to look up a city, region and country, and then discards it and writes 0.0.0.0 to client_IP. It replaces the whole address, not just the last octet. To store real addresses, set DisableIpMasking to true on the Application Insights resource; it affects only data ingested afterwards, and Microsoft recommends leaving it off. Behind a proxy, server telemetry reads X-Forwarded-For and uses the last address in the list, which may be the proxy rather than the user.

People usually arrive at this question from one of two directions: a security review asking why IP addresses aren’t in the logs, or a dashboard where every user seems to live next to the data centre. Both come down to the same two steps the service performs on every item.

What happens to the address

When telemetry reaches Azure, the ingestion service:

  1. Looks up the address and fills client_City, client_StateOrProvince and client_CountryOrRegion (ClientCity, ClientStateOrProvince and ClientCountryOrRegion in the workspace).
  2. Discards the address and writes 0.0.0.0 to client_IP (ClientIP).

Microsoft describes this as deliberate, to avoid collecting personal data and location information you didn’t ask for. It’s a full replacement, not the last-octet masking some older blog posts describe. And when no address is collected at all, no city or country is filled in either.

Where the address comes from depends on the telemetry:

  • Browser telemetry (the JavaScript SDK): the ingestion endpoint takes the sender’s address from the incoming connection. That’s the user’s public address, or their corporate proxy’s.
  • Server telemetry (requests, dependencies and the rest from your back end): the Application Insights module collects the client address of the incoming request, using the X-Forwarded-For header when a proxy sets it. When that list has more than one address, the last one is used for geolocation.

Proxies, gateways and the wrong city

By convention each proxy appends to X-Forwarded-For the address it received the request from, so the list grows by one entry per hop. With a single proxy in front of the app, the last entry is the user. With two (a CDN in front of an application gateway, say), the last entry can be the first proxy, and every user is placed wherever that proxy runs.

When that happens, override the address with the one you trust. Your proxy chain decides which entry that is; a common choice is the address your edge proxy reports in its own header.

Classic SDK 2.x

In the .NET SDK 2.x (deprecated, retiring March 31, 2027) the work is done by ClientIpHeaderTelemetryInitializer, which sets Context.Location.Ip from X-Forwarded-For. To use a different value, add an initializer of your own; Microsoft’s documentation shows one for exactly this purpose:

using Microsoft.ApplicationInsights.Channel;
using Microsoft.ApplicationInsights.DataContracts;
using Microsoft.ApplicationInsights.Extensibility;
using Microsoft.AspNetCore.Http;

public class TrustedClientIpInitializer : ITelemetryInitializer
{
    private readonly IHttpContextAccessor _http;
    public TrustedClientIpInitializer(IHttpContextAccessor http) => _http = http;

    public void Initialize(ITelemetry telemetry)
    {
        if (telemetry is not RequestTelemetry request) return;
        var ip = _http.HttpContext?.Request.Headers["X-Client-IP"].ToString();
        if (!string.IsNullOrEmpty(ip)) request.Context.Location.Ip = ip;
    }
}

Register it with builder.Services.AddHttpContextAccessor() and builder.Services.AddSingleton<ITelemetryInitializer, TrustedClientIpInitializer>(). The header name here is an example: use the one your edge proxy sets and strips from incoming requests. The value is used for the lookup and then masked like any other unless masking is disabled. See telemetry initializers for registration in other app types.

The retired Node.js 2.x SDK has no initializers; its telemetry processors receive each envelope and can change it before it is sent.

Today: SDK 3.x and the Microsoft OpenTelemetry Distro

SDK 3.x removed ClientIpHeaderTelemetryInitializer along with the other built-in initializers; in classic ASP.NET its job is done by an internal processor. Location.Ip remains a public context property, so a manually created TelemetryClient or an individual item can still set it:

var request = new RequestTelemetry("GET /orders", DateTimeOffset.Now, TimeSpan.FromMilliseconds(120), "200", true);
request.Context.Location.Ip = clientIp;
telemetryClient.TrackRequest(request);

With OpenTelemetry, the address used for geolocation comes from a span attribute. Which one depends on the language:

LanguageAttributeNotes
.NETclient.addressOnly on server activities (requests)
Node.jsclient.addresshttp.client_ip is accepted as an alternate
Pythonhttp.client_ipSet in a span processor
JavaSet automaticallyNo code needed

In .NET, set it in an activity processor registered before the distro:

using System.Diagnostics;
using OpenTelemetry;

public class ClientIpProcessor : BaseProcessor<Activity>
{
    public override void OnEnd(Activity activity)
    {
        if (activity.Kind == ActivityKind.Server && TrustedIp.Current is { } ip)
        {
            activity.SetTag("client.address", ip);
        }
    }
}

TrustedIp.Current stands for however your app reads the trusted address for the current request. The stored client_IP still follows the masking rule.

One path ignores the switch entirely. Telemetry sent through native OTLP ingestion (an OTLP endpoint into a data collection rule, stored in OTelSpans and OTelLogs) always records 0.0.0.0 in ClientIP, whatever DisableIpMasking says. client.address is still used for the lookup on that path, but the address isn’t kept.

Storing real IP addresses

Microsoft’s default and recommendation is not to collect IP addresses; if you do, check it against your compliance requirements and local law first. The switch is a property of the Application Insights component, DisableIpMasking. The quickest way to set it is Azure PowerShell:

Update-AzApplicationInsights -ResourceGroupName "rg-shop" -Name "appi-shop-prod" -DisableIPMasking

In ARM or Bicep, set DisableIpMasking: true inside the component’s properties. Template deployments and the REST Components - Create Or Update call replace the resource rather than patching it, so keep every other property, including WorkspaceResourceId, in what you send.

Two consequences to plan for: only telemetry ingested after the change shows real addresses (older rows stay 0.0.0.0), and the addresses then live in your workspace under its retention. Microsoft suggests marking the relevant tables as protected tables, which block standard and custom read roles until you grant access explicitly.

Before you switch masking off

Most reasons people give for storing addresses are served by the columns you already have. A map of users by country, a split of failures by region or a check that a CDN is routing Europe to the European origin all work from client_CountryOrRegion and client_City, which are filled with masking on. Real addresses earn their place in narrower cases: investigating abuse from specific sources, or matching telemetry against firewall and gateway logs during an incident.

If you do enable it, decide three things first:

  • Who can read it. Protected tables and workspace access control limit the audience; a shared workspace makes every reader an IP reader.
  • How long it stays. Table-level retention lets the request table keep addresses for less time than the rest of the workspace.
  • What your privacy notice says. Microsoft’s guidance is to consult the privacy rules that apply to you before storing identifiers.

Also remember that location is only as good as the address. Users behind a corporate proxy or VPN appear wherever that network leaves the internet, with masking on or off.

Troubleshooting

SymptomLikely cause
client_IP is always 0.0.0.0Default masking; expected unless DisableIpMasking is true
Still 0.0.0.0 after enablingLooking at data ingested before the change, or the data came through native OTLP
City empty for server telemetryNo client address collected (for example, background work with no incoming request)
Everyone in one cityAddress of a proxy or load balancer used; fix X-Forwarded-For or set the address yourself
Template edit “didn’t save” the propertyThe exported template never shows it; check with Get-AzResource -ExpandProperties

To check what a resource is collecting, query the location columns directly:

requests
| where timestamp > ago(1d)
| summarize requests = sum(itemCount) by client_CountryOrRegion, client_City, client_IP
| top 20 by requests

It counts with itemCount because of sampling.

Queries for this topic

From the KQL library: explained queries for the Application Insights tables, ready to copy.

  • Returns: One row per client country or region with its sampling-adjusted request count, its share of all non-synthetic requests and its p95 server duration in milliseconds.

  • Returns: Up to 50 rows, one per page name and country or region, with the sampling-adjusted page view count and the number of distinct users, most viewed first.

Frequently asked questions

Does Application Insights mask only the last octet of the IP address?

No. Microsoft's documentation describes the whole address being discarded after the geolocation lookup, with 0.0.0.0 written to client_IP. Only the city, state or province, and country or region columns keep anything from it.

Can I turn IP collection back off after enabling it?

Yes. Set DisableIpMasking back to false on the resource. Addresses already stored stay in the tables until their retention ends, so plan for that when you enable it, and use protected tables or table-level retention to limit who can read them and for how long.

Why does every user appear to be in the same city as my data centre?

The address used for the lookup is your proxy's or load balancer's, not the user's. Make sure the proxy sets X-Forwarded-For, and remember that the last address in that list is used. Where several proxies append to it, set the client address yourself from the hop you trust.

Sources, checked

  1. Application Insights geolocation and IP address handlinglearn.microsoft.com · checked
  2. Add and modify OpenTelemetry in Application Insights (Set the user IP)learn.microsoft.com · checked
  3. Monitor .NET and Node.js applications with Application Insights (Classic API 2.x)learn.microsoft.com · checked
  4. Migration guidance: Application Insights 2.x to 3.x (telemetry context)github.com · checked
  5. AppRequests table referencelearn.microsoft.com · checked