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.
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: 80Jump 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,
KongPluginandKongClusterPlugin. - 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:
- Takes the client address that Kong's own trusted proxy handling produced (see Client IP selection).
- Removes any
X-IPGeo-*header the client sent, so it cannot fake its location or reputation. - Looks the address up in each database, in the order you listed them.
- For each field, takes the value from the first database that has it. List order is priority order.
- 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 (
kongin 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-ipgeolocationLuaRocks 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.rockspec2. 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,ipgeolocationOr 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
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: standardThe 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 PK2. Block countries in Kong
Serve a service only in Germany, Austria and Switzerland, and answer everyone else with 451 :
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 region3. Stop VPNs, proxies and attackers in front of a login page
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: 80A 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 = onexamples/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.
| Database | File | What 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
| Parameter | Type | Default | Description |
|---|---|---|---|
databases | array of strings, required | Absolute 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
| Parameter | Type | Default | Description |
|---|---|---|---|
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
| Parameter | Type | Default | Description |
|---|---|---|---|
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 | boolean | Block Tor exit nodes ( is_tor ). | |
policy.block_vpn | boolean | Block VPN endpoints ( is_vpn ). | |
policy.block_proxy | boolean | Block proxies ( is_proxy ). | |
policy.block_relay | boolean | Block privacy relays such as iCloud Private Relay ( is_relay ). | |
policy.block_residential_proxy | boolean | Block residential proxies ( is_residential_proxy , from the Security or Residential Proxy database). | |
policy.block_anonymous | boolean | Block any anonymizing service ( is_anonymous ). | |
policy.block_known_attacker | boolean | Block known attackers ( is_known_attacker ). | |
policy.block_bot | boolean | Block bots ( is_bot ). Known good bots such as search engine crawlers are spared. | |
policy.block_known_good_bots | boolean | Make block_bot apply to known good bots as well. | |
policy.block_spam | boolean | Block known spam sources ( is_spam ). | |
policy.block_cloud_provider | boolean | Block cloud and hosting provider addresses ( is_cloud_provider , from the Security or Hosting database). | |
policy.block_corporate_gateway | boolean | Block corporate egress gateways such as secure web gateways ( is_corporate_gateway ). | |
policy.block_threat_score_above | integer | unset | Block 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 | boolean | Pass private, loopback and link-local clients without a lookup and without headers. | |
policy.allow_unknown | boolean | For 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 | boolean | When 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 | boolean | Evaluate 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
| Parameter | Type | Default | Description |
|---|---|---|---|
log_serialize | boolean | Add 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.
| Preset | Headers |
|---|---|
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/falseor1/0, set byheaders.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. Theasn_numberfield 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:
- countries (
allowed_countriesorblocked_countries) - continents (
allowed_continentsorblocked_continents) - autonomous systems (
allowed_asnsorblocked_asns) - security flags, in the order of the policy table
- 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_unknowndecides: pass by default, block when set tofalse. - The threat score must be greater than the threshold.
block_threat_score_above: 80blocks 81 and above, never 80. - Known good bots are spared by
block_botunlessblock_known_good_botsis 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 behind | kong.conf |
|---|---|
| nothing, clients connect directly | nothing 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 beforecors(2000), every authentication plugin andip-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
rewritephase, 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 theaccessphase, 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:
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 decisionTreat it as read only. These combinations are covered by the end to end tests:
- Rate limiting per country:
rate-limitingwithlimit_by: headerandheader_name: X-IPGeo-Country-Code. - Renaming headers:
request-transformer, for examplerename.headers: ["X-IPGeo-City-Name:X-City"]. - Custom logic:
post-functionor your own plugin readingkong.ctx.shared.ipgeolocation. - Logging: with
log_serialize: true, http-log, file-log, tcp-log, udp-log and syslog receive anipgeolocationobject 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:
1policy:
2 block_cloud_provider: true
3 block_bot: true
4 exempt_ips: [198.51.100.0/24]2. Rate limit each country separately
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-Code3. Enrich access logs for analytics and fraud review
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.logEach 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
| Situation | Behavior |
|---|---|
| A database is missing or invalid at startup | Logged once per distinct error and retried every 30 seconds without blocking traffic. Requests follow fail_open . |
| One of several databases is unavailable | The 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 replacement | The 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 use | The mapped copy keeps serving until the next reload or restart. |
| A database record is malformed | The lookup fails safely within bounds and work limits, and the request follows fail_open . |
| IPv6 client, IPv4 only database | No data from that database. This is not a failure. |
| The client address is not an IP address, for example a Unix socket | Follows fail_open . |
| The address is in no database | No 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:
| Operation | Time |
|---|---|
| IPv4 / IPv6 search tree lookup | 0.78 µs / 1.91 µs |
Location lookup, minimal preset fields | 3.2 µs |
Location lookup, standard preset fields | 6.6 µs |
| Location lookup, every field | 12.3 µs |
| Security lookup, fields for a typical security policy | 3.3 µs |
Location + ASN + Security, standard preset plus policy fields | 14.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:
| Configuration | Added CPU per request |
|---|---|
| Security policy only, no headers, 1 database | 15 µs |
minimal preset, Location + ASN | 21 µs |
standard preset, Location + ASN + Security, with or without policy | 33 µ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
.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.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.kong/kong Helm chart and configure it with KongPlugin or KongClusterPlugin . See Installation.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.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.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.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.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.fail_open , which allows them unless you set it to false . See Failure handling.::ffff:203.0.113.7 are looked up as IPv4.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:
- Nginx module, the same databases as native nginx variables ( documentation)
- Traefik plugin, the same databases as a Traefik middleware
- mmdbio, a command line tool for reading and inspecting MMDB files
- IPGeolocation CLI, IP intelligence from your terminal and shell scripts
- Integration guides, setup guides for every integration
- All repositories
Account, data and support:
- Sign up for a free account
- Database documentation and schemas
- Database pricing and bundles
- IP geolocation API or database: which one to use
- What is IP geolocation and how does it work
- Contact support
- Service status
Kong and format references:
- Kong Gateway documentation
- Kong Pongo, the plugin test harness used by this repository
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.