umami icon indicating copy to clipboard operation
umami copied to clipboard

Create custom headers to use in `detect.ts` > `getLocation` to get the country when using proxy + Cloudflare

Open etx121 opened this issue 4 months ago • 4 comments

Describe the feature or enhancement

Hello,

I am currently self-hosting Umami, and I want to proxy the Umami traffic to bypass ad-blockers. I am also using Cloudflare and Next.js. My traffic follows this flow: User -> Cloudflare -> Proxy (the same Next JS app) -> Cloudflare -> Umami However, because of the proxy, I have all the events marked as happening in the country of the proxy instead of being the country of the user. I tried to overcome the issue by using CLIENT_IP_HEADER in order to read the header containing the IP address of the client. But then I realize that to get the location of the user, you are using the Cloudflare headers:

  • cf-ipcountry
  • cf-region-code
  • cf-ipcity

As I am using Cloudflare everywhere in the flow, Cloudflare is resetting these headers, so no matter the IP address sent to you, you will mark the traffic belonging to the country of the proxy.

I saw that 4 years ago, you were using COUNTRY_IP_HEADER for only one commit: https://github.com/umami-software/umami/blob/b1ced5f32ce149e9f6ed309478203c2b80363297/lib/request.js#L55-L58

So, would you be able to reintroduce these kind of mechanisms, so I can bypass the latest location of Cloudflare? Otherwise, I would need to use the Maxmind location, but I am not sure how accurate they are compared to Cloudflare. The function I am talking about is https://github.com/umami-software/umami/blob/60eaaaff60a38efb3c779a250742187ec9201944/src/lib/detect.ts#L90-L148

A solution could be to add in getLocation from src/lib/detect.ts this code (this rejected commit https://github.com/umami-software/umami/pull/3153):

export async function getLocation(ip: string = '', headers: Headers, hasPayloadIP: boolean) { 
  // Ignore local ips
  if (await isLocalhost(ip)) {
    return;
  }

  if (!hasPayloadIP && !process.env.SKIP_LOCATION_HEADERS) {
    const customHeader = String(
      process.env.CLIENT_IPCOUNTRY_HEADER
    ).toLowerCase();

    if (customHeader !== "undefined" && req.headers[customHeader]) {
      const regionHeader = String(
        process.env.CLIENT_IPREGION_HEADER
      ).toLowerCase();
      const cityHeader = String(process.env.CLIENT_IPCITY_HEADER).toLowerCase();

      const country = req.headers[customHeader];
      const subdivision1 = req.headers[regionHeader];
      const city = req.headers[cityHeader];

      return {
        country,
        subdivision1: getRegionCode(country, subdivision1),
        city,
      };
    }

    // Cloudflare headers
    if (headers.get("cf-ipcountry")) {
      // ....
    }
    // ....
  }
}

Would it be possible to have some systems like that?

Also, at the moment, I set this environment variable SKIP_LOCATION_HEADERS to true in self-hosted Umami instance (could be great to document it). Now, it seems to show the good country. However, I don't know if using Maxmind and GeoLite2-City.mmdb has a cost?

etx121 avatar Aug 16 '25 20:08 etx121

Since you would have to generate the custom headers anyways, how about manipulating the payload and sending country, region, and city fields?

mikecao avatar Aug 21 '25 17:08 mikecao

Hi @mikecao ,

I am manipulating it, but both the proxy and the website are under Cloudflare. Thus Cloudflare is overriding the value of the country/city/region when at the proxy from my experimentation. I log this at the proxy level and they are correct (I am not using next rewrites as it was not working to get the good location. Thus, I think the implementation made https://github.com/umami-software/umami/pull/3153 could solve the issue. I thought you were extracting location from the IP address, but in fact you extract it from explicit headers, and those ones are changed by Cloudflare when proxied. I tried Maxmind free DB, but I feel like it is inaccurate.

I don't know if this issue might be linked to the other issue you are trying to solve currently https://github.com/umami-software/umami/issues/3583

etx121 avatar Aug 22 '25 10:08 etx121

Hi @mikecao , Does my change make sense in your opinion? No matter what I do, I cannot change the location of the user unless I set SKIP_LOCATION_HEADERS to true. But then Maxmind free DB is not giving accurate location. And Cloudflare location headers have already been overwritten when passing the proxy.

etx121 avatar Sep 01 '25 09:09 etx121

Hello @mikecao , Do you think we can add a header to change to custom header then? This code solved the issue: https://github.com/umami-software/umami/pull/3153

etx121 avatar Oct 11 '25 08:10 etx121

This issue is stale because it has been open for 60 days with no activity.

github-actions[bot] avatar Dec 11 '25 02:12 github-actions[bot]