> For the complete documentation index, see [llms.txt](https://docs.openiam.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.openiam.com/administration-guide/platform/geo-ip-lookup.md).

# Set up geo-IP lookup

Turn on IP geolocation, choose between the external API and the local database, load the database file, and prove one address resolves.

## What this is, and when you need it

Geolocation turns a client IP address into a country, city and coordinates. Exactly one thing in OpenIAM consumes it: adaptive authentication risk. `IS_NEW_CITY`, `IS_NEW_COUNTRY` and `MEMBER_OF_COUNTRY` — and so any rule that branches on where a sign-in came from — are inert until this resolves. Come here before you build such a rule, and come back when a rule that used to challenge people has quietly stopped.

Treat the answer as evidence about a network address, not proof of where a person is.

## At a glance

|                          |                                                                                                                                                                                                                         |
| ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **You will end up with** | **Use Geolocation** on, one source selected, and **Test IP lookup** returning a country and city for a real address                                                                                                     |
| **Where**                | **Administration → GEO location DB**, the screen titled **IP Geolocation Settings** at `/webconsole/ip-location-manage`                                                                                                 |
| **You need first**       | A decision on whether this deployment may make outbound HTTP calls, one real client IP to test with, and — for the local-database route — the deployment owner, because the file has to be placed on the ESB filesystem |
| **Takes**                | 15 minutes for the external-API route; a day or more for the local-database route, most of it waiting for the file                                                                                                      |
| **Reversible**           | Yes — every setting here is a property you can set back. Addresses already resolved keep their cached answer until the cache is cleared                                                                                 |

## Before you begin

The local-database route has a prerequisite you cannot satisfy yourself: the file named in **Database File Name** is read from the ESB's own filesystem, so somebody with filesystem access has to put it there. From this screen you can read the path they must fill and confirm afterwards that they filled it. Start that conversation before step 1 — see the [Installation Guide](/installation/installation.md).

## Turn geolocation on and prove one address resolves

{% stepper %}
{% step %}

### Open the IP Geolocation Settings screen

Go to **Administration** → **GEO location DB**. The screen has three panels: **IP Geolocation Configuration**, **IP Geolocation Database Info.** and **IP Lookup Result**. The lookup field is pre-filled with the client IP OpenIAM read from *your* request, which makes it the fastest test you have.
{% endstep %}

{% step %}

### Decide the source before you touch a switch

Two sources, and the choice is a deployment constraint rather than a preference. If this deployment may make outbound HTTP calls to a third party, use the custom API and leave **Use Custom API** on. If it may not, or must not send client addresses off-site, use the local database and turn **Use Custom API** off.

{% hint style="danger" %}
**The shipped default calls db-ip.com over plain HTTP.** `org.openiam.ip.geo.use.custom.api` ships `true` and the shipped resolver reads JSON from `http://api.db-ip.com/v2/free/<ip>`, so an installation that enables geolocation without changing anything sends every client address it sees to a third party in clear text. Replace the script, or turn **Use Custom API** off and run the local database, before enabling geolocation in production.
{% endhint %}
{% endstep %}

{% step %}

### Switch Use Geolocation on

The master switch, `org.openiam.adaptive.risk.geo.location.use`, ships `false`. Nothing resolves a country until it is on, and no risk type reports that it is off — a location rule simply stops branching.
{% endstep %}

{% step %}

### Set the source switches

On the custom-API route, leave **Custom Groovy Script** at `geo/IPGeoAPIDefaultScript.groovy` unless you are replacing the resolver, leave **Save new IPs to database** on so answers accumulate locally, and set **Search IP Location in Local DB** on if you want the local table tried first and the API called only on a miss — the cheapest way to cut outbound traffic without giving it up.

On the local-database route, the other three switches have no effect.
{% endstep %}

{% step %}

### Save

Saving writes the six `IP` properties and refreshes the property cache. This screen and **Administration → System Configuration → IP** are two front ends over the same rows, so they cannot disagree. The save is audited with the description *"Save IP geo location config"*.
{% endstep %}

{% step %}

### For the local database, confirm the path and have the file placed

Read the value in **Database File Name**. It ships as `/data/openiam/conf/iamscripts/geo/db/dbip-city-lite.csv.gz` and **the file at that path is not shipped** — what ships beside it is a small sample named `dbip-city-lite-test.csv` and a note giving the download page, `https://db-ip.com/db/download/ip-to-city-lite`. Give the deployment owner that path and that URL. A CSV or a gzipped CSV both work; compression is detected from the file itself, not the extension.
{% endstep %}

{% step %}

### Click Upload Database, then watch Database Status

The load runs in the background one file at a time, so the screen returns before it finishes. **Database Status** under **IP Geolocation Database Info.** is the only report of the outcome: `NOT_UPLOADED`, `UPLOADING`, `SUCCESS` or `FAIL`. A missing file fails immediately and the screen shows `FILE_NOT_EXIST`.
{% endstep %}

{% step %}

### Read Row count against the file you were given

Do this before you trust a local lookup. See [The loader stops at about ten thousand rows](#the-loader-stops-at-about-ten-thousand-rows).
{% endstep %}

{% step %}

### Test one address with Test IP lookup

Enter a real client address and read **IP Lookup Result**: **IP Address**, **Country Code**, **Country Name**, **Continent Code**, **Continent Name**, **City**, **State / Province**, **District**, **ZIP / Postal Code**, **Time Zone** and **Weather Code**. An address the source cannot resolve returns `404` and no panel. Test *through* the reverse proxy or you will not see [the problem below](#the-address-openiam-actually-resolves).
{% endstep %}
{% endstepper %}

## The loader stops at about ten thousand rows

{% hint style="danger" %}
The loader reads roughly **10,002 rows** and then sets **Database Status** to `SUCCESS`. The db-ip city-lite file has millions of ranges, so a "successful" upload can leave you with a fraction of the database and a status that says everything is fine. Compare **Row count** with the line count of the file you were given; if it reads about ten thousand, this is what happened, and no address outside that first slice of the space will resolve locally.

Two smaller traps in the same loader. **Row count** is the total number of rows in the lookup table, not the number this upload added, so it also counts single addresses cached by **Save new IPs to database**. And the first line of the file is parsed as data, not as a header — a header row whose first column is not an address fails the whole load and leaves **Database Status** at `FAIL`. The db-ip downloads are headerless, so this only bites a file somebody has edited.
{% endhint %}

### Which file to ask for

The dialect is detected from the number of comma-separated columns on the first line, so you never configure it — but you do have to ask for the right download.

| Columns                    | Detected as       | Resolves                                                          |
| -------------------------- | ----------------- | ----------------------------------------------------------------- |
| 3                          | `IP_COUNTRY_LITE` | Country only                                                      |
| 4                          | `IP_COUNTRY`      | Continent and country                                             |
| 8, last column numeric     | `IP_CITY_LITE`    | Continent, country, state, city, coordinates                      |
| 8, last column not numeric | `IP_ISP`          | Continent, country, ISP, AS number, connection type, organization |
| 14                         | `IP_LOCATION`     | Adds district, postcode, time zone and weather code               |
| 18                         | `IP_LOCATION_ISP` | Everything above                                                  |

Any other column count is treated as `IP_CITY_LITE`, which means a file in an unrecognised shape is silently misparsed rather than rejected. `IP_CITY_LITE` is the shape the three location risk types need.

## Two things the switch labels do not tell you

When the API is the source and returns nothing — unreachable, or a result carrying an error code or the country code `ZZ`, both of which the shipped resolver discards — OpenIAM falls back to the local table **regardless of Search IP Location in Local DB**. And lookups are cached per address, so **changing the source does not re-answer an address you have already looked up**: it keeps its old answer until the cache is cleared, which in practice means an ESB restart. Test with an address you have not used before.

The full six-property table is in [Adaptive risk types and their values](/administration-guide/security/adaptive-authentication/adaptive-authentication-reference.md#geolocation), under the labels the property tab uses rather than the ones here — **Use Geolocation** is *Activate Geolocation in Adaptive Risk* there, and **Custom Groovy Script** is *Custom IP Location API Script*.

## The address OpenIAM actually resolves

OpenIAM resolves the **leftmost value of the `X-Forwarded-For` header**, falling back to the socket peer address when the header is absent. A proxy that does not set the header hands you the proxy's own address, so every user resolves to one location. A proxy that *appends* to a client-supplied header rather than replacing it hands you an address the client chose — for a location-based authentication rule, a value an attacker controls. Neither is fixable inside OpenIAM: correcting the forwarded-for chain is a proxy change for the deployment owner, described in [Access Gateway reference](/administration-guide/gateway/access-gateway-reference.md#forwarded-headers-soap-and-header-based-auth). Test through the proxy so you see what OpenIAM sees.

## Verification

| What to check           | How                                                                                                                                                                                                                                                                                    |
| ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| The settings were saved | The audit event described *"Save IP geo location config"*, then reopen the screen and compare all six values                                                                                                                                                                           |
| The database load ran   | Audit action `UPLOAD_IP_GEO_DB`, then **Database Status** = `SUCCESS` **and** **Row count** consistent with the line count of the file you were given                                                                                                                                  |
| A lookup resolves       | **Test IP lookup** on an address you have not queried before returns a country and city rather than `404`                                                                                                                                                                              |
| The consumer sees it    | Sign in as a test user, then confirm `city` and `country` are populated on that user's most recent `urifederationstatistic` document — the check that actually matters, described in [Adaptive authentication](/administration-guide/security/adaptive-authentication.md#verification) |

## When it goes wrong

| Symptom                                                          | Inspect                                                                | Fix                                                                                                                                                                                                                                    |
| ---------------------------------------------------------------- | ---------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Nothing resolves for any address                                 | **Use Geolocation**, then **Use Custom API**                           | The master switch ships `false`. If it is on, the API is unreachable or the local table is empty                                                                                                                                       |
| Every user resolves to the same place, usually the load balancer | The `X-Forwarded-For` header the proxy sets, and whether it is trusted | OpenIAM resolves the leftmost value of that header. Fix it at the proxy; nothing in OpenIAM can recover the real address                                                                                                               |
| A source change has no effect on addresses already seen          | Whether you retested with a *new* address                              | Results are cached per address. Use an untested address, or restart the ESB                                                                                                                                                            |
| Upload fails and the screen shows `FILE_NOT_EXIST`               | **Database File Name**, then whether that file is present on the ESB   | The shipped default names a file that is not redistributed. The placement is the deployment owner's, not yours                                                                                                                         |
| **Database Status** is `SUCCESS` but lookups miss                | **Row count**                                                          | The loader stops at about 10,002 rows and still reports success                                                                                                                                                                        |
| **Database Status** is `FAIL` on a file that exists              | The first line of the file                                             | The first line is parsed as data. A header row fails the load                                                                                                                                                                          |
| An adaptive rule has stopped challenging anybody, with no error  | Whether `city` and `country` are empty on new statistics documents     | Location risk types that fail open take the `Y` edge when the lookup fails. See [What happens when geolocation is unavailable](/administration-guide/security/adaptive-authentication.md#what-happens-when-geolocation-is-unavailable) |

## Related

* [Adaptive authentication](/administration-guide/security/adaptive-authentication.md) — the only consumer of geolocation, and the fail-open behaviour when it stops resolving
* [Adaptive risk types and their values](/administration-guide/security/adaptive-authentication/adaptive-authentication-reference.md#geolocation) — the six properties with their shipped values, types and property-tab labels
* [System configuration](/administration-guide/platform/system-configuration.md) — the `IP` property tab behind this screen

*Applies to OpenIAM 2026.8.1. Last reviewed 7 August 2026.*


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.openiam.com/administration-guide/platform/geo-ip-lookup.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
