Kong IP Geolocation Plugin for IPGeolocation.io

Overview

Geo-blocking, VPN and proxy detection, and IP intelligence headers for Kong Gateway, powered by IPGeolocation.io MMDB databases. The plugin reads the databases from local disk and answers every lookup inside Kong's worker processes, with no API calls and no per-request cost.

Use it to block traffic by country, stop Tor exit nodes, VPNs, proxies and known attackers before they reach your services, route visitors by region, and add country, city, ASN and threat data to upstream requests and access logs.

Response Preview
1# Block Tor and known attackers on /login, and tell every backend where visitors are
2plugins:
3  - name: ipgeolocation
4    route: login
5    config:
6      databases:
7        - /usr/local/share/ipgeolocation/db-ip-location.mmdb
8        - /usr/local/share/ipgeolocation/db-ip-security.mmdb
9      headers:
10        preset: standard
11      policy:
12        block_tor: true
13        block_known_attacker: true
14        block_threat_score_above: 80

Jump to: Installation | Quick start | Databases | Configuration | Headers | Security policy | Client IP | Updates | Troubleshooting | FAQ


Why use this plugin

Most Kong geolocation setups either call a remote API on every request or need a custom Kong build with a GeoIP library compiled in. This plugin ships as plain Lua, loads IPGeolocation.io databases once per worker, and answers each lookup locally in microseconds ( benchmarks).

  • No API calls and no per-request billing. Every lookup is a read from a local file.
  • Nothing to compile. The MMDB reader is pure LuaJIT, so it runs on a stock Kong Gateway with no libmaxminddb , Redis or extra Lua rocks.
  • Decisions at the edge. Block or route in Kong before the request reaches authentication or your upstream.
  • One plugin, many databases. Location, Security, Company, ASN, Abuse Contact, Hosting and Residential Proxy all load through one list, and the plugin layers them.
  • Headers your backend can trust. The plugin removes every client supplied X-IPGeo-* header before it adds its own.
  • Kong native. Global, Service and Route scopes, DB-less, PostgreSQL and hybrid mode, KongPlugin and KongClusterPlugin .
  • The same field names, header names and presets as the IPGeolocation.io Nginx module and Traefik plugin, so a backend sees the same headers whichever gateway sits in front of it.

How it works

You list one or more .mmdb files under databases. Kong opens each file in every worker when the configuration loads, then for every request the plugin:

  1. Takes the client address that Kong's own trusted proxy handling produced (see Client IP selection).
  2. Removes any X-IPGeo-* header the client sent, so it cannot fake its location or reputation.
  3. Looks the address up in each database, in the order you listed them.
  4. For each field, takes the value from the first database that has it. List order is priority order.
  5. Sets the headers you asked for, shares the result with other plugins, evaluates the policy, then forwards or blocks.

Because the first match wins, databases layer: load a Location and a Security database together, and country_code comes from the first while is_vpn comes from the second.

The files are memory mapped. The kernel shares the mapped pages between all workers, and only the pages that lookups touch are read from disk.


Requirements

  • Kong Gateway 3.x on Linux. Tested end to end on Kong Gateway OSS 3.9.3 (DB-less) and with Kong's test harness on 3.9.2 against PostgreSQL and DB-less. Kong Gateway 3.10 LTS and 3.14 LTS use the same plugin APIs and are expected to work, but have not been tested yet.
  • One or more IPGeolocation.io .mmdb databases.
  • Read access to the database files for Kong's worker user ( kong in the official packages and images), including every directory above them.

Installation

Install the plugin on every Kong node that runs it. In hybrid mode that includes the control planes, which validate the configuration. Only the data planes need the database files.


1. Install with LuaRocks

The plugin is published on LuaRocks:

luarocks install kong-plugin-ipgeolocation

LuaRocks needs curl or wget to download. The official kong Docker image ships with neither, and LuaRocks then fails with the misleading No results matching query were found for Lua 5.1 . Install curl first, or use the Docker image below.

To install from a checkout instead:

git clone https://github.com/IPGeolocation/kong-plugin-ipgeolocation.git
cd kong-plugin-ipgeolocation
luarocks make kong-plugin-ipgeolocation-0.1.0-1.rockspec

2. Install in a Docker image

Build an image with the plugin baked in from examples/docker/Dockerfile:

docker build -f examples/docker/Dockerfile -t kong-ipgeolocation .

examples/docker/docker-compose.yml runs Kong in DB-less mode next to an updater container that downloads the databases into a shared volume.


3. Install on Kubernetes

The plugin's modules live in one directory, so a single ConfigMap carries the whole plugin, and the kong/kong Helm chart loads it with plugins.configMaps :

kubectl create configmap kong-plugin-ipgeolocation -n kong --from-file=kong/plugins/ipgeolocation

examples/kubernetes has Helm values with a database volume, an init container and an updater sidecar, plus KongPlugin and KongClusterPlugin resources and an annotated Ingress.


4. Enable the plugin

Add the plugin to kong.conf and restart Kong:

plugins = bundled,ipgeolocation

Or set KONG_PLUGINS=bundled,ipgeolocation in the environment.


Quick start

These examples use DB-less declarative configuration. The same settings work through the Admin API ( POST /plugins with name=ipgeolocation ) and as a Kubernetes KongPlugin resource.


1. Add geolocation headers for your backend

Response Preview
1_format_version: "3.0"
2services:
3  - name: api
4    url: http://api.internal:8080
5    routes:
6      - name: api
7        paths: [/]
8plugins:
9  - name: ipgeolocation
10    config:
11      databases:
12        - /usr/local/share/ipgeolocation/db-ip-location.mmdb
13        - /usr/local/share/ipgeolocation/db-ip-asn.mmdb
14      headers:
15        preset: standard

The upstream then receives headers like these for a client in Lahore:

X-IPGeo-Country-Code: PK
X-IPGeo-Country-Name: Pakistan
X-IPGeo-Continent-Code: AS
X-IPGeo-State-Code: PK-PB
X-IPGeo-City-Name: Lahore
X-IPGeo-Zip-Code: 54000
X-IPGeo-Latitude: 31.54972
X-IPGeo-Longitude: 74.34361
X-IPGeo-Time-Zone: Asia/Karachi
X-IPGeo-ASN: AS64500
X-IPGeo-Organization-Name: Example Telecom PK

2. Block countries in Kong

Serve a service only in Germany, Austria and Switzerland, and answer everyone else with 451 :

Response Preview
1plugins:
2  - name: ipgeolocation
3    service: shop
4    config:
5      databases: [/usr/local/share/ipgeolocation/db-ip-country.mmdb]
6      headers: { preset: none }
7      policy:
8        allowed_countries: [DE, AT, CH]
9        allow_unknown: false
10        status_code: 451
11        message: This service is not available in your region

3. Stop VPNs, proxies and attackers in front of a login page

Response Preview
1plugins:
2  - name: ipgeolocation
3    route: login
4    config:
5      databases: [/usr/local/share/ipgeolocation/db-ip-security.mmdb]
6      headers: { preset: none }
7      policy:
8        block_tor: true
9        block_vpn: true
10        block_proxy: true
11        block_residential_proxy: true
12        block_known_attacker: true
13        block_threat_score_above: 80

A blocked client receives 403 with Kong's standard error body ( {"message":"Access denied","request_id":"..."} ) and never reaches the service. The reason is logged but never sent to the client.


4. Run Kong behind a load balancer

If Kong sits behind a load balancer at 10.0.0.0/8 , tell Kong to trust it so the plugin looks up the visitor and not the load balancer:

trusted_ips = 10.0.0.0/8
real_ip_header = X-Forwarded-For
real_ip_recursive = on

examples/kong.yml is a larger example with per country routing, rate limiting per country, a dry run rollout and logging.


Getting the databases

Download the .mmdb files from your IPGeolocation.io account and point databases at them. Each database has a static download link that never changes, so the same link serves the first download and every update. The link contains your API key, so keep it in a secret and out of your Kong configuration.

DatabaseFileWhat it adds
IP Geolocation db-ip-location.mmdb or db-ip-city.mmdb country, state, district, city, postal code, coordinates, time zone, currency; the Advance tier adds accuracy radius, confidence and connection type
Country db-ip-country.mmdb country, continent and currency, in a smaller file
ISP db-ip-isp.mmdb or db-ip-city-isp.mmdb ISP, ASN and connection type, with country or full location
IP Security db-ip-security.mmdb threat score, Tor, VPN, proxy, relay, residential proxy, bot, spam, attacker, cloud and corporate gateway flags
IP Company db-ip-company.mmdb company or ISP name, domain and type
IP to ASN db-ip-asn.mmdb AS number, name, organization, type and domain; the Extended tier adds RIR, allocation, routes and peers
IP Abuse Contact db-ip-abuse.mmdb abuse email, phone, address, route and country
Residential Proxy db-residential-proxy.mmdb residential proxy provider and last seen date
IP Hosting db-ip-hosting.mmdb hosting provider name

Combined products ship two files in one archive, for example City + Security as db-ip-city.mmdb and db-ip-security.mmdb . List both files. Some combined files hold several data sets in one file, such as db-ip-city-company-asn.mmdb .

Load only what you need. Fields from a database you did not load stay empty, so referencing them is safe. docs/DATABASES.md lists every file, the products that ship it and the fields it provides. Tiers and bundles are on the pricing page, and the database documentation shows the full schemas.


Configuration reference

The plugin can be applied globally, to a Service or to a Route. It cannot be scoped to a Consumer, because it runs before authentication, when the consumer is not known yet. Supported protocols are http , https , grpc and grpcs .


1. Databases

ParameterTypeDefaultDescription
databases array of strings, requiredAbsolute paths of 1 to 16 MMDB files, in priority order. For every field, the first database that has a non-empty value wins. Paths are checked for syntax only, so a control plane does not need the files.
database_refresh_interval integer 0 Seconds between checks for updated files. 0 turns checks off; otherwise the minimum is 60 . A changed file is opened and validated before it replaces the one in service. See Keeping databases up to date.

2. Headers

ParameterTypeDefaultDescription
headers.preset string minimal Headers to add: none , minimal , standard or full . See Request headers.
headers.custom map {} Header name to field name, applied on top of the preset. Adds headers with any name ( X-Country: country_code ). An empty field name removes a header the preset added ( X-IPGeo-Latitude: "" ). Client supplied copies of custom headers are removed too. Field names are listed in docs/FIELDS.md.
headers.boolean_format string true_false Booleans as true / false (the Traefik plugin's format) or 1 / 0 with one_zero (the Nginx module's format).
headers.list_separator string , Separator for list values such as VPN provider names, 1 to 8 characters.
headers.language string en Language of country, region, city, continent and currency names: en , de , ru , ko , pt , ja , fa , fr , zh , es , cs or it . Falls back to English where a translation is missing.
headers.max_value_length integer 1024 Maximum header value length in bytes, 16 to 8192. Longer values are cut on a UTF-8 character boundary.

3. Policy

ParameterTypeDefaultDescription
policy.allowed_countries array of strings [] Only these ISO 3166-1 alpha-2 country codes may pass. Cannot be combined with blocked_countries .
policy.blocked_countries array of strings [] These country codes are blocked.
policy.allowed_continents array of strings [] Only these continents may pass: AF , AN , AS , EU , NA , OC , SA . Cannot be combined with blocked_continents .
policy.blocked_continents array of strings [] These continents are blocked.
policy.allowed_asns array of strings [] Only these autonomous systems may pass ( AS15169 or 15169 ). Cannot be combined with blocked_asns .
policy.blocked_asns array of strings [] These autonomous systems are blocked.
policy.block_tor booleanCrossBlock Tor exit nodes ( is_tor ).
policy.block_vpn booleanCrossBlock VPN endpoints ( is_vpn ).
policy.block_proxy booleanCrossBlock proxies ( is_proxy ).
policy.block_relay booleanCrossBlock privacy relays such as iCloud Private Relay ( is_relay ).
policy.block_residential_proxy booleanCrossBlock residential proxies ( is_residential_proxy , from the Security or Residential Proxy database).
policy.block_anonymous booleanCrossBlock any anonymizing service ( is_anonymous ).
policy.block_known_attacker booleanCrossBlock known attackers ( is_known_attacker ).
policy.block_bot booleanCrossBlock bots ( is_bot ). Known good bots such as search engine crawlers are spared.
policy.block_known_good_bots booleanCrossMake block_bot apply to known good bots as well.
policy.block_spam booleanCrossBlock known spam sources ( is_spam ).
policy.block_cloud_provider booleanCrossBlock cloud and hosting provider addresses ( is_cloud_provider , from the Security or Hosting database).
policy.block_corporate_gateway booleanCrossBlock corporate egress gateways such as secure web gateways ( is_corporate_gateway ).
policy.block_threat_score_above integerunsetBlock when threat_score (0 to 100, higher is riskier) is greater than this value.
policy.exempt_ips array of strings [] Addresses or CIDRs that skip every policy rule. They are still enriched. To deny addresses, use Kong's ip-restriction plugin.
policy.allow_private booleanTickPass private, loopback and link-local clients without a lookup and without headers.
policy.allow_unknown booleanTickFor allow lists only: let a request pass when its country, continent or ASN is not in the databases. While a database is unavailable and fail_open is true , unknown values always pass.
policy.fail_open booleanTickWhen a database is missing, unreadable or corrupt, or the client address cannot be parsed, allow the request ( true ) or block it ( false ).
policy.dry_run booleanCrossEvaluate and log the policy but never block. Adds X-IPGeo-Dry-Run with the reason to the upstream request.
policy.status_code integer 403 Status code for blocked requests, 400 to 599. For example 451 for legal geo restrictions.
policy.message string Access denied Message for blocked requests. The reason is never shown to the client.

4. Logging

ParameterTypeDefaultDescription
log_serialize booleanCrossAdd the lookup result and policy decision to Kong's log serializer under ipgeolocation , for http-log, file-log, tcp-log and similar plugins.

Validation rejects relative paths, .. segments, refresh intervals between 1 and 59 seconds, unknown presets and fields, invalid header names, headers the plugin must not set ( Host , Content-Length , Authorization , Cookie and other hop-by-hop or framing headers), malformed country codes, continents, ASNs and CIDRs, combined allow and block lists, and status codes outside 400 to 599.


Request headers

Header names are X-IPGeo- followed by the field name in title case, with common acronyms in capitals. country_code becomes X-IPGeo-Country-Code , is_vpn becomes X-IPGeo-Is-VPN and asn becomes X-IPGeo-ASN . These are the headers the Traefik plugin sets.

PresetHeaders
none none. Use headers.custom , or only enforce the policy.
minimal X-IPGeo-Country-Code , X-IPGeo-City-Name , X-IPGeo-ASN
standard country code and name, continent code, state code, city, zip code, latitude, longitude, time zone, ASN, organization name, threat score, X-IPGeo-Is-VPN , X-IPGeo-Is-Proxy , X-IPGeo-Is-Tor
full all 81 bounded fields plus X-IPGeo-IP , the address that was looked up

full leaves out the ASN routing lists ( asn_routes , asn_peers , asn_upstreams , asn_downstreams ), which can hold thousands of entries. Map them with headers.custom if you need them. All 85 fields, their headers and the database paths they are read from are in docs/FIELDS.md.


1. How values are formatted

  • Missing data means no header. A field the databases do not have, or have as an empty string, is not sent, and a header is never sent with an empty value. An address without a record in the Security Database is not flagged, so its security headers are absent and not false .
  • Booleans are true / false or 1 / 0 , set by headers.boolean_format , whatever the database stores.
  • Numbers keep the database's own text ( 31.54972 ), so values match the Nginx and Traefik integrations.
  • ASNs are written as AS64500 . The asn_number field gives the bare number.
  • Lists are joined with headers.list_separator .
  • Control characters, including CR and LF, are removed, and values are capped at headers.max_value_length , so database content can never inject headers.

2. Spoofed header protection

Before it adds anything, the plugin removes every request header whose name starts with X-IPGeo- , in any letter case and also when written with underscores ( X_IPGeo_Country_Code , which some backend frameworks treat as the same name). Headers mapped in headers.custom are removed the same way, and all request headers are examined however many the client sends. This happens on every request the plugin handles, including private and exempt clients. A client that sends X-IPGeo-Country-Code: US from Pakistan reaches the backend as PK .


Security policy

Rules are evaluated in a fixed order, and the first rule that blocks decides:

  1. countries ( allowed_countries or blocked_countries )
  2. continents ( allowed_continents or blocked_continents )
  3. autonomous systems ( allowed_asns or blocked_asns )
  4. security flags, in the order of the policy table
  5. threat score ( block_threat_score_above )

How the plugin treats missing data:

  • Block lists, flags and the threat score block only on positive evidence. The Security Database covers flagged ranges, so an address without a record is not flagged.
  • Allow lists need a known value. When the country, continent or ASN is unknown, allow_unknown decides: pass by default, block when set to false .
  • The threat score must be greater than the threshold. block_threat_score_above: 80 blocks 81 and above, never 80.
  • Known good bots are spared by block_bot unless block_known_good_bots is set.
  • Exempt and private clients skip the policy ( exempt_ips , allow_private ).
  • A missing or broken database is never treated as a reason to block. It follows fail_open.

Blocked requests receive policy.status_code with Kong's standard error body containing policy.message , formatted for the client's Accept header like Kong's other plugins. The reason is logged at info level and available to log plugins.


Client IP selection

The plugin looks up kong.client.get_forwarded_ip() , the client address after Kong's real IP processing. It never parses X-Forwarded-For itself. Configure Kong, not the plugin:

Kong is behindkong.conf
nothing, clients connect directlynothing to configure
a load balancer that sets X-Forwarded-For trusted_ips = <load balancer addresses> , real_ip_header = X-Forwarded-For , real_ip_recursive = on
a CDN that sends the client address in its own header, such as Cloudflare trusted_ips = <the CDN's published ranges> , real_ip_header = <that header> , for example CF-Connecting-IP
a load balancer using the PROXY protocol proxy_listen = 0.0.0.0:8000 proxy_protocol , trusted_ips = <load balancer addresses> , real_ip_header = proxy_protocol

With real_ip_recursive = on , Kong walks X-Forwarded-For from the right and stops at the first address that is not trusted, so entries a client adds in front cannot change the result. IPv4-mapped IPv6 addresses such as ::ffff:203.0.113.7 are looked up as IPv4.

When trusted_ips is not set, Kong ignores X-Forwarded-For and uses the TCP peer. Behind a load balancer that peer is usually a private address, which the plugin skips by default ( allow_private ), so no headers appear. To check which address is being looked up, map the pseudo field ip to a header: headers.custom: { X-IPGeo-IP: ip } .


Scopes, phases and routing

  • Priority 2450. The plugin runs after bot-detection (2500) and before cors (2000), every authentication plugin and ip-restriction (990). Blocked requests never reach authentication or the upstream.
  • Precedence. As with every Kong plugin, a Route instance overrides a Service instance, which overrides the global instance, and only one instance runs per request.
  • Routing on enrichment. A global instance enriches in the rewrite phase, before Kong routes the request, so routes can match on its headers. A spoofed header cannot steer routing because it is replaced first. Route and Service instances run in the access phase, after routing. When one of them overrides a global instance, the headers the global instance set are removed and the more specific configuration applies.

Send German traffic to a dedicated service:

Response Preview
1routes:
2  - name: de
3    paths: [/shop]
4    headers:
5      x-ipgeo-country-code: [DE]
6    service: shop-de
7  - name: default
8    paths: [/shop]
9    service: shop
10plugins:
11  - name: ipgeolocation          # global: runs before routing
12    config:
13      databases: [/usr/local/share/ipgeolocation/db-ip-country.mmdb]
14      headers: { preset: minimal }

Using the results in other plugins

Plugins that run later (lower priority) can read the headers and the shared context:

local geo = kong.ctx.shared.ipgeolocation
-- geo.ip        the address that was looked up
-- geo.found     true when any database had a record
-- geo.private   true when the address was skipped as private
-- geo.exempt    true when the address matched exempt_ips
-- geo.fields    typed values: geo.fields.country_code == "PK", geo.fields.is_vpn == true,
--               geo.fields.threat_score == 60, geo.fields.vpn_provider == { "Nord VPN" }
-- geo.blocked, geo.dry_run, geo.reason   the policy decision

Treat it as read only. These combinations are covered by the end to end tests:

  • Rate limiting per country: rate-limiting with limit_by: header and header_name: X-IPGeo-Country-Code .
  • Renaming headers: request-transformer , for example rename.headers: ["X-IPGeo-City-Name:X-City"] .
  • Custom logic: post-function or your own plugin reading kong.ctx.shared.ipgeolocation .
  • Logging: with log_serialize: true , http-log, file-log, tcp-log, udp-log and syslog receive an ipgeolocation object with the address, the resolved fields and the policy decision.

Use ip-restriction for static address allow and deny lists. It runs after this plugin.


Real world examples


1. Keep hosting providers and bots off a public API

Search engine crawlers stay allowed, and a partner range is exempt:

Response Preview
1policy:
2  block_cloud_provider: true
3  block_bot: true
4  exempt_ips: [198.51.100.0/24]

2. Rate limit each country separately

Response Preview
1plugins:
2  - name: ipgeolocation
3    config:
4      databases: [/usr/local/share/ipgeolocation/db-ip-country.mmdb]
5      headers: { preset: minimal }
6  - name: rate-limiting
7    config:
8      minute: 600
9      limit_by: header
10      header_name: X-IPGeo-Country-Code

3. Enrich access logs for analytics and fraud review

Response Preview
1plugins:
2  - name: ipgeolocation
3    config:
4      databases:
5        - /usr/local/share/ipgeolocation/db-ip-location.mmdb
6        - /usr/local/share/ipgeolocation/db-ip-security.mmdb
7      headers: { preset: none }
8      log_serialize: true
9  - name: file-log
10    config:
11      path: /var/log/kong/requests.log

Each log entry then has an ipgeolocation object with the country, city, ASN, threat score and flags of the client.


Keeping databases up to date

IPGeolocation.io updates its databases daily. examples/updater/ipgeolocation-update.sh downloads them, checks checksum.txt , optionally checks IPGeolocation.io's signature with their public key, validates each file and installs it. Give it the download links through the IPGEO_URLS environment variable from a secret. It never writes the links' query strings, which hold your API key, to its log.

There are two ways to make Kong use the new files:

  • kong reload , the default. New workers open the new files and old workers finish their requests with the old ones. This works everywhere, including with refresh turned off. The updater runs it for you with -r .
  • database_refresh_interval . Each worker checks the file at that interval and swaps in a validated replacement without a reload. If the replacement does not validate, the database in service stays. Workers swap independently, so for up to one interval plus 10 seconds different workers can serve different releases.

On Kubernetes, prefer storage local to the pod, such as an emptyDir filled by an init container and refreshed by a sidecar as in examples/kubernetes, or a rolling restart. Do not replace files on a network filesystem shared across nodes while Kong has them mapped, because a file replaced from another node can become a stale handle on the node that maps it.


Failure handling

SituationBehavior
A database is missing or invalid at startupLogged once per distinct error and retried every 30 seconds without blocking traffic. Requests follow fail_open .
One of several databases is unavailableThe others still enrich. With fail_open: true their values are still enforced and unknown values pass allow lists; with false the request is blocked.
A refresh finds an invalid replacementThe database in service stays and the error is logged. The same broken file is not retried until it changes.
A database is deleted while in useThe mapped copy keeps serving until the next reload or restart.
A database record is malformedThe lookup fails safely within bounds and work limits, and the request follows fail_open .
IPv6 client, IPv4 only databaseNo data from that database. This is not a failure.
The client address is not an IP address, for example a Unix socketFollows fail_open .
The address is in no databaseNo headers. Allow lists consult allow_unknown , and nothing else blocks.

With fail_open: false , a request is blocked whenever the plugin cannot reach a decision, and the reason recorded is IP intelligence unavailable . Unavailability is logged at most once a minute per worker.


Performance and memory

Measured on one vCPU of an Intel Xeon at 2.1 GHz with IPGeolocation.io's sample databases (search trees of up to 9.5 million nodes) and LuaJIT 2.1 in OpenResty. Database work alone:

OperationTime
IPv4 / IPv6 search tree lookup0.78 µs / 1.91 µs
Location lookup, minimal preset fields3.2 µs
Location lookup, standard preset fields6.6 µs
Location lookup, every field12.3 µs
Security lookup, fields for a typical security policy3.3 µs
Location + ASN + Security, standard preset plus policy fields14.3 µs (IPv4), 22.2 µs (IPv6)

Inside Kong (one worker, ab on the same vCPU, Kong answering directly with request-termination at about 40,000 requests per second), the plugin adds this much CPU per request:

ConfigurationAdded CPU per request
Security policy only, no headers, 1 database15 µs
minimal preset, Location + ASN21 µs
standard preset, Location + ASN + Security, with or without policy33 µs

About half of the standard figure is database work, a fifth is setting 15 headers, and the rest is header stripping and Kong's own per plugin work. To keep the cost down, request only the headers your services use, and use preset: none where you only need the policy.

Opening 305 MiB of databases added 1.2 MiB of resident memory. Pages are read on demand and shared by all workers through the page cache, so plan for the page cache to hold the parts of your databases that traffic touches. For heavy and varied traffic that can be their full size, and the full Security Database is several GiB. bench/bench.lua reproduces the database measurements on your own databases and hardware.


Troubleshooting

Log says database ... is unavailable: ...Permission denied . Kong's worker user ( kong ) cannot read the file or a directory above it. Fix the ownership or modes, then wait 30 seconds or run kong reload .

No X-IPGeo-* headers at all. The address being looked up is private, usually your load balancer's. Configure trusted_ips and real_ip_header as described in Client IP selection, and map ip to a header to see the address.

Every request gets the same location. Same cause: Kong sees the load balancer and not the client.

One header is missing. The databases you loaded have no value for that field, or the value is empty for that address. docs/DATABASES.md lists the fields of every database file. To inspect a record directly, use mmdbio.

An updated database is not used. Refresh is off by default. Run kong reload , or set database_refresh_interval .

Log says ... was modified in place , or workers exit with signal 7 ( SIGBUS ) after an update. Your update process rewrites the file. Download to a temporary file in the same directory and rename it, as described in Keeping databases up to date.

schema violation on a path. Paths must be absolute file paths without .. . Kong does not check that the file exists when it validates the configuration.

Everything is blocked, or nothing is. Turn on dry_run and read the reasons in the log. allow_private skips internal traffic, and allow_unknown decides what happens to addresses that no database covers.

block_bot lets Googlebot through. This is by design. Crawlers are flagged as bots and as known good bots, and blocking them removes you from search results. Set block_known_good_bots: true if you really want them blocked.


Frequently asked questions

No. It reads local .mmdb files and makes no outbound requests on the request path, so there are no API quotas and no per-request costs, and Kong keeps working if the network to IPGeolocation.io is down. Only the updater talks to IPGeolocation.io, to download new releases.
It depends on what you block or send to your backend. Country and city headers or geo-blocking need the IP Geolocation database, or the smaller Country database for country rules only. VPN, proxy, Tor, bot and threat score rules need the Security Database. ASN filtering needs IP to ASN, and ISP or company names need IP Company. You can load several together and the plugin layers them, so start with one and add more later without changing your rules. The full list is in Getting the databases.
Most use libmaxminddb through FFI or need a custom Kong or OpenResty build with an nginx GeoIP module. This plugin has its own MMDB reader in pure LuaJIT, so it installs on a stock Kong with LuaRocks or a ConfigMap. It is built for IPGeolocation.io schemas, layers multiple databases, exposes security and ASN data as well as location, and can block on any of it.
Yes. The plugin is tested in DB-less mode and with PostgreSQL. In hybrid mode, install it on the control planes and the data planes, and put the database files on the data planes only. On Kubernetes, load it with a ConfigMap through the kong/kong Helm chart and configure it with KongPlugin or KongClusterPlugin . See Installation.
Yes. Apply the plugin globally so it runs before Kong's router, then match routes on the X-IPGeo-Country-Code header. A client cannot steer routing with a fake header, because the plugin replaces it first. See Scopes, phases and routing.
No. The plugin removes every client supplied header in the X-IPGeo- namespace, in any letter case and with underscores, before it adds its own. It also removes client copies of your custom header names. See Spoofed header protection.
Yes. Set Kong's trusted_ips to the proxy's addresses and real_ip_header to the header that carries the client address, such as X-Forwarded-For or CF-Connecting-IP . Without that, every visitor looks like your load balancer. See Client IP selection.
About 15 µs of CPU per request for a security policy without headers, and about 33 µs with the standard preset across three databases. Cost grows with the number of headers and rules you use, not with the size of the database. The numbers and the benchmark setup are in Performance and memory.
Set database_refresh_interval (for example 3600 ) and have a scheduled job replace the files atomically. The plugin notices the change, validates the new file, swaps it in and keeps serving the old copy if the new one does not open. You can also run kong reload after each update. See Keeping databases up to date.
Traffic keeps flowing by default. A missing database is retried every 30 seconds, a broken replacement never replaces the database in service, and requests follow fail_open , which allows them unless you set it to false . See Failure handling.
Yes. IPGeolocation.io databases cover both address families, and the plugin looks up whichever address Kong resolved for the client. Every rule and header works the same for IPv6, and IPv4-mapped addresses such as ::ffff:203.0.113.7 are looked up as IPv4.
Yes. The Nginx module and the Traefik plugin read the same files and use the same field names, header names and presets. The Kong plugin always strips the whole X-IPGeo- namespace, always takes the client address from Kong's trusted proxy handling, and leaves the long ASN routing lists out of the full preset. Dry run, exemptions, a configurable status code and message, and log integration are Kong additions.

Development

luacheck .                       # lint
busted                           # unit tests (spec/unit), no Kong needed
pongo run spec/integration       # Kong's test harness: schema and proxy behavior, PostgreSQL and DB-less
python3 spec/e2e/e2e.py          # a real Kong (kong and resty on PATH), end to end

docs/DEVELOPMENT.md covers the code layout, the test fixtures, the cross check against python-maxminddb , benchmarks and the release process. Contributions are welcome, see CONTRIBUTING.md. Report vulnerabilities as described in SECURITY.md, and read docs/SECURITY_REVIEW.md for the threat model.


Related tools and links

IPGeolocation.io tools:

Account, data and support:

Kong and format references:


License

MIT. See LICENSE.

Built for IPGeolocation.io databases. Questions about the data, tiers or bundles are answered on the database documentation and pricing pages.

Subscribe to Our Newsletter

Get the latest in geolocation tech, straight to your inbox.