Fluent Bit IP Geolocation and Threat Enrichment with IPGeolocation.io


Overview

Enrich your Fluent Bit logs with IP geolocation, VPN and proxy detection, and network data. Fluent Bit's built-in geoip2 filter reads IPGeolocation.io's downloadable IP databases in MMDB format, so every record that carries an IP address can leave Fluent Bit with:

All IPGeolocation.io IP databases are supported, not only these three. That includes the IP to Country, IP to City and IP to ISP databases, the IP to Company Database, IP Abuse Contact Database, IP WHOIS Database, IP to Hosting Database and Residential Proxy Database, and combined databases that hold several of these in one file.

The lookups run against files on your own machine. There are no API calls and no plugin to install. To decide whether a database or an API suits your workload, see IP geolocation API vs database.


Why use IPGeolocation.io databases with Fluent Bit

  • Location and security in one pass. Country and city sit next to VPN, proxy, residential proxy, Tor, relay and bot flags and a threat score in the same record.
  • Every IP database works. Geolocation, security, ASN, company, ISP, abuse contact, WHOIS, hosting and residential proxy data all come in MMDB format and use the same filter.
  • IPv4 and IPv6. Each file covers both address families.
  • Names in 12 languages. City, region and country names are stored in English, Czech, German, Spanish, Persian, French, Italian, Japanese, Korean, Portuguese, Russian and Chinese.
  • Fresh data without downtime. Databases are updated daily or weekly, depending on your plan, and Fluent Bit can load a new file without a restart.
  • Private and predictable. Lookups happen locally. IP addresses in your logs never leave your network, and there is no per-request cost.

Common use cases

  • Security monitoring. Flag logins and API calls from VPNs, proxies and Tor, and send them to your SIEM.
  • Fraud and abuse prevention. Spot sign-ups and checkouts from anonymizing networks or hosting providers.
  • Traffic analytics. Chart requests by country, region and city in Grafana, Kibana or OpenSearch Dashboards.
  • Incident response. See which network an attacking address belongs to, and who to contact about it.
  • Compliance reporting. Show where your traffic comes from, using data that never leaves your infrastructure.

What is Fluent Bit?

Fluent Bit is a lightweight, open source log and metrics processor and forwarder, and a graduated project of the Cloud Native Computing Foundation. It collects data from files, containers, systemd and network inputs, transforms it with filters, and sends it to destinations such as Elasticsearch, OpenSearch, Grafana Loki, Splunk, Kafka and Amazon S3. It often runs as a DaemonSet in Kubernetes or as an agent on each server.

Enriching logs with IP data inside Fluent Bit means the data reaches your backend already enriched. Dashboards, alerts and queries can use the country or the VPN flag directly, with no lookup at query time.


How it works

  1. When Fluent Bit starts, each geoip2 filter opens one MMDB file.
  2. For each record that matches the filter, it reads the IP address from the field named in lookup_key .
  3. It looks the address up and, for each record rule, copies one value from the database into a new key.
  4. The record continues to the next filter or output.

Each filter reads one file, so you add one geoip2 filter per database file.


1. What is a path?

An MMDB file stores a nested record for each IP range. A path names one value inside that record, with a dot between each level. Here is part of the record for 37.120.202.92 in the IP Geolocation Database:

Response Preview
1{
2  "location": {
3    "city": {
4      "name": {
5        "en": "Secaucus",
6        "de": "Secaucus"
7      }
8    },
9    "coordinates": {
10      "latitude": "40.78834",
11      "longitude": "-74.05502"
12    },
13    "country": {
14      "code2": "US",
15      "name": {
16        "en": "United States"
17      }
18    },
19    "state": {
20      "name": {
21        "en": "New Jersey"
22      }
23    }
24  },
25  "time_zone": "America/New_York"
26}

The path location.country.code2 leads to "US" , and location.city.name.en leads to "Secaucus" . In a Fluent Bit rule, you wrap the path in %{...} .


Requirements

ComponentVersion
Fluent Bit5.1.3 (tested). The official container images and packages include the geoip2 filter.
IPGeolocation.io databasesAny IP database in MMDB format. Download them from your IPGeolocation.io account, or visit IP database pricing to choose a plan.
Your logsRecords that carry the client IP address in a field. The examples use a field named remote_addr .

Quick start

This walkthrough runs Fluent Bit in Docker with a test record, so you can see the enrichment working in a few minutes. It uses the IP Geolocation, IP Security and IP to ASN databases.


1. Step 1: Put the databases in a folder

Download the MMDB files from your account and place them in a folder named databases :

Response Preview
1databases/
2├── db-ip-asn.mmdb
3├── db-ip-location.mmdb
4└── db-ip-security.mmdb

2. Step 2: Create the configuration

Save this as fluent-bit.yaml next to the databases folder. It creates one test record and adds location, security and ASN data to it:

Response Preview
1service:
2  flush: 1
3  log_level: warn
4
5pipeline:
6  inputs:
7    - name: dummy
8      tag: web
9      dummy: '{"remote_addr": "37.120.202.92", "method": "POST", "path": "/login"}'
10      samples: 1
11
12  filters:
13    - name: geoip2
14      match: web
15      database: /usr/local/share/ipgeolocation/db-ip-location.mmdb
16      lookup_key: remote_addr
17      record:
18        - country_code remote_addr %{location.country.code2}
19        - country_name remote_addr %{location.country.name.en}
20        - region remote_addr %{location.state.name.en}
21        - city remote_addr %{location.city.name.en}
22        - latitude remote_addr %{location.coordinates.latitude}
23        - longitude remote_addr %{location.coordinates.longitude}
24        - time_zone remote_addr %{time_zone}
25
26    - name: geoip2
27      match: web
28      database: /usr/local/share/ipgeolocation/db-ip-security.mmdb
29      lookup_key: remote_addr
30      record:
31        - threat_score remote_addr %{threat_score}
32        - is_vpn remote_addr %{is_vpn}
33        - is_proxy remote_addr %{is_proxy}
34        - is_tor remote_addr %{is_tor}
35        - is_anonymous remote_addr %{is_anonymous}
36
37    - name: geoip2
38      match: web
39      database: /usr/local/share/ipgeolocation/db-ip-asn.mmdb
40      lookup_key: remote_addr
41      record:
42        - asn remote_addr %{asn.as_number}
43        - as_organization remote_addr %{asn.organization}
44
45  outputs:
46    - name: stdout
47      match: '*'
48      format: json_lines

3. Step 3: Run Fluent Bit

docker run --rm \
  -v "$PWD/databases":/usr/local/share/ipgeolocation:ro \
  -v "$PWD/fluent-bit.yaml":/fluent-bit/etc/fluent-bit.yaml:ro \
  fluent/fluent-bit:5.1.3 -c /fluent-bit/etc/fluent-bit.yaml

4. Step 4: Check the output

Fluent Bit prints one JSON line per record. Here it is formatted for readability:

Response Preview
1{
2  "date": 1791266086.766181,
3  "remote_addr": "37.120.202.92",
4  "method": "POST",
5  "path": "/login",
6  "country_code": "US",
7  "country_name": "United States",
8  "region": "New Jersey",
9  "city": "Secaucus",
10  "latitude": "40.78834",
11  "longitude": "-74.05502",
12  "time_zone": "America/New_York",
13  "threat_score": 50,
14  "is_vpn": "true",
15  "is_proxy": "true",
16  "is_tor": "false",
17  "is_anonymous": "true",
18  "asn": "9009",
19  "as_organization": "M247 Europe SRL"
20}

The test address is a VPN exit, so is_vpn is "true" . Values depend on your database release, and the date key comes from the stdout output. To see the network behind the address, with its routes, peers and WHOIS data, open AS9009 (M247 Europe SRL) in the IPGeolocation.io ASN browser.

Once this works, replace the dummy input and stdout output with your real input and output. The access log example further down shows a typical setup.


Configuration reference


1. Filter settings

SettingWhat it does
name Always geoip2 .
match The tag of the records to enrich, such as web or nginx.* . Records with other tags pass through unchanged.
database Path to the MMDB file. In Docker, this is the path inside the container.
lookup_key The record field that holds the IP address.
record One rule per new key. A filter can have as many rules as you need.

2. Record rules

Each record rule has three parts:

Response Preview
1<new key> <field that holds the IP> %{<path in the database>}

For example, country_code remote_addr %{location.country.code2} reads the IP from remote_addr , looks up location.country.code2 , and stores the result in a new key named country_code . The field in the rule is normally the same as lookup_key .


3. Paths in each database

Every IPGeolocation.io IP database works with the filter. Each one stores its values under its own paths, and location data uses the same paths in every database that includes it.

DatabasePaths
IP Geolocation Database and IP to City DatabaseUnder location. , such as location.country.code2 , plus time_zone
IP to Country DatabaseUnder location.country. , such as location.country.code2
IP Security DatabaseAt the top level, such as is_vpn and threat_score
IP to ASN DatabaseUnder asn. , such as asn.as_number
IP to Company DatabaseUnder company. , such as company.name.en
IP to ISP DatabaseAt the top level: isp , asn , as_organization
IP Abuse Contact DatabaseUnder abuse. , such as abuse.emails
IP WHOIS DatabaseUnder whois. , such as whois.organization.name
IP to Hosting DatabaseAt the top level: hosting_provider
Residential Proxy DatabaseAt the top level: proxy_provider and last_seen

Two details to watch with ISP data:

  • asn is the AS number itself ( %{asn} ), not a group ( %{asn.as_number} ).
  • In the IP to ISP Database, the country sits at the top level ( %{country.code2} ), not under location. .

ISP and ASN fields describe different things. See how an ISP differs from an ASN.


4. More security fields

The IP Security Database has more fields than the quick start uses. Add any of these to the security filter in the same way:

PathMeaning
is_residential_proxy Residential proxy
is_relay Relay service, such as iCloud Private Relay
is_known_attacker Known source of attacks
is_bot Automated traffic
is_spam Known spam source
is_cloud_provider Address belongs to a cloud provider
vpn_confidence_score Confidence in the VPN result, 0 to 100
proxy_confidence_score Confidence in the proxy result, 0 to 100
cloud_provider_name Name of the cloud provider

See the field reference for every path, and the IP Security Database documentation for what each field means.


5. Reading a combined database

Some plans deliver several databases in one file, such as location, company and ASN data together. The file keeps each database's paths, so one filter can read all of them:

Response Preview
1    - name: geoip2
2      match: web
3      database: /usr/local/share/ipgeolocation/db-ip-city-company-asn.mmdb
4      lookup_key: remote_addr
5      record:
6        - country_code remote_addr %{location.country.code2}
7        - city remote_addr %{location.city.name.en}
8        - asn remote_addr %{asn.as_number}
9        - company remote_addr %{company.name.en}

6. Classic configuration format

If you use the classic .conf format, the same filter looks like this:

[FILTER]
    Name       geoip2
    Match      web
    Database   /usr/local/share/ipgeolocation/db-ip-location.mmdb
    Lookup_key remote_addr
    Record     country_code remote_addr %{location.country.code2}
    Record     city         remote_addr %{location.city.name.en}

Enriching a web server access log

In production, Fluent Bit usually reads a log file. This configuration tails an Nginx access log, parses it with Fluent Bit's built-in nginx parser, and enriches each request. The parser puts the client address in a field named remote , so that is the lookup_key .

Response Preview
1service:
2  flush: 1
3  log_level: warn
4  parsers_file: /fluent-bit/etc/parsers.conf
5
6pipeline:
7  inputs:
8    - name: tail
9      path: /var/log/nginx/access.log
10      tag: nginx.access
11      parser: nginx
12
13  filters:
14    - name: geoip2
15      match: nginx.*
16      database: /usr/local/share/ipgeolocation/db-ip-location.mmdb
17      lookup_key: remote
18      record:
19        - country_code remote %{location.country.code2}
20        - city remote %{location.city.name.en}
21
22  outputs:
23    - name: stdout
24      match: '*'
25      format: json_lines

/fluent-bit/etc/parsers.conf is the path in the official container image. Package installs keep it at /etc/fluent-bit/parsers.conf . In Docker, also mount the log directory into the container.

For this log line:

37.120.202.92 - - [06/Oct/2026:10:15:32 +0000] "POST /login HTTP/1.1" 401 512 "-" "Mozilla/5.0"

Fluent Bit prints (formatted for readability):

Response Preview
1{
2  "date": 1791281732.0,
3  "remote": "37.120.202.92",
4  "host": "-",
5  "user": "-",
6  "method": "POST",
7  "path": "/login",
8  "code": "401",
9  "size": "512",
10  "referer": "-",
11  "agent": "Mozilla/5.0",
12  "country_code": "US",
13  "city": "Secaucus"
14}

If your server sits behind a load balancer or CDN, the first field in the log is the proxy's address, not the visitor's. Configure the server to log the client address, for example with Nginx's real_ip module. See how to get the real client IP address behind a proxy.


Routing VPN, proxy and Tor traffic

Enrichment becomes more useful when you act on it. This example sends records from anonymizing networks to their own destination, such as your SIEM, and leaves the rest of the traffic on its normal path. Add the rewrite_tag filter after the security filter:

  filters:
    # ... the geoip2 filters from the quick start ...

    - name: rewrite_tag
      match: web
      rule: $is_anonymous ^true$ security.anonymous false

  outputs:
    - name: stdout            # normal traffic
      match: web
    - name: stdout            # VPN, proxy and Tor traffic; replace with your SIEM output
      match: security.*

The rule reads: when is_anonymous is true , give the record the tag security.anonymous . The final false means the record is not also kept under the original tag. To route on one signal only, use another flag, such as $is_tor ^true$ or $is_vpn ^true$ .

The flags are strings, so match them with the ^true$ pattern shown above.


Working with the values

ValueTypeExampleNotes
Security flags ( is_vpn , is_tor , ...)String "true" Compare as strings, not booleans.
Scores ( threat_score , confidence scores)Integer 50 0 to 100.
CoordinatesString "40.78834" Convert to numbers for geo points (below).
AS numberString "9009" No AS prefix.
Provider namesList ["Private Internet Access VPN"] Read the first entry with .0 (below).
Missing valueEmpty string "" The database has no value for that address.
Address not in the database null null Every key from that filter is null .

Coordinates as numbers. Fluent Bit's type_converter filter converts the coordinate strings to numbers, which most backends need for a geo point. Add it after the location filter:

Response Preview
1    - name: type_converter
2      match: web
3      str_key:
4        - latitude lat float
5        - longitude lon float

Lists. The filter cannot copy a whole list: it writes null and logs Not supported MAP and ARRAY . To get the first provider name, use %{vpn_provider_names.0} . Most addresses have an empty list, and the filter logs a warning for each of those records, so add list fields only when you need them.

Languages. Replace .en in a name path with cs , de , es , fa , fr , it , ja , ko , pt , ru or zh . A name without a translation is an empty string.


Running in production

  • Enrich only what you need. Set match to the tags that carry client IP addresses, and add only the fields your dashboards and alerts use.
  • Use the real client IP. Behind a proxy, load balancer or CDN, look up the visitor's address, not the proxy's.
  • Keep the logs quiet. A wrong path logs a warning for every record. Test new rules with the quick start before you deploy them.
  • Kubernetes. Put the databases in a volume and mount it read-only at the same path in every Fluent Bit pod. After you update the files, reload Fluent Bit or restart the DaemonSet with kubectl rollout restart daemonset/<name> .
  • Keep the databases current. Automate updates as shown in the next section.

Keeping the databases up to date

IPGeolocation.io updates the databases daily or weekly, depending on your plan. Fluent Bit opens each file when it starts and keeps reading that copy, so a new file on disk takes effect only after a reload or restart.

To reload without stopping Fluent Bit, turn on hot reload:

Response Preview
1service:
2  hot_reload: on

Fluent Bit then reopens its files when it receives SIGHUP ( kill -HUP <pid> , or docker kill --signal HUP <container> in Docker).

This script installs a release safely. A release arrives as a ZIP archive with the MMDB file of each database in your plan, a README.md , and a checksum.txt that lists a SHA-256 hash for every file. The script unpacks the archive beside the live files, refuses it if a hash or a database check fails, moves the new MMDB files into place and sends Fluent Bit a SIGHUP . Set DOWNLOAD_URL to the MMDB download link in your IPGeolocation.io account. Besides curl , the script needs unzip , sha256sum and the mmdbio command-line tool:

#!/bin/sh
# Install a new IPGeolocation.io database release and reload Fluent Bit.
set -eu

DB_DIR=/usr/local/share/ipgeolocation
DOWNLOAD_URL="<MMDB download link from your IPGeolocation.io account>"

# 1. Unpack the release in a temporary folder beside the live databases.
WORK=$(mktemp -d "$DB_DIR/.release.XXXXXX")
trap 'rm -rf "$WORK"' EXIT
curl -fsSL -o "$WORK/release.zip" "$DOWNLOAD_URL"
# -DD stamps the unpacked files with the current time, not the archive's.
unzip -q -DD "$WORK/release.zip" -d "$WORK"
rm "$WORK/release.zip"

# 2. Refuse the release if any file fails its checksum or any database is damaged.
(cd "$WORK" && sha256sum --quiet -c checksum.txt)
for db in "$WORK"/*.mmdb; do
    mmdbio verify --db "$db"
done

# 3. Move the new databases over the old ones. Each rename is atomic.
for db in "$WORK"/*.mmdb; do
    mv "$db" "$DB_DIR/"
done

# 4. Make Fluent Bit reopen its files (requires hot_reload: on).
kill -HUP "$(pidof fluent-bit)"

The file names inside the archive stay the same from release to release, so the database paths in your configuration never change. After a failed check, the script exits with an error and removes its temporary folder, and Fluent Bit carries on with the files it already has. Run it from cron on your plan's release schedule.

In Docker, mount the directory that holds the databases, as in the quick start, not the individual files. A file mounted on its own keeps pointing at the old copy after the rename.


Troubleshooting

No new keys appear. The record's tag does not match the filter's match setting, so the filter skips it. Check the tag your input sets.

Every key is null . The address is not in that database, or the record has no lookup_key field. To see what a database holds for an address, use mmdbio:

mmdbio read --db /usr/local/share/ipgeolocation/db-ip-location.mmdb --ip 37.120.202.92

cannot get value: The lookup path does not match the data . The path does not exist in that database. Check the spelling, and check that the filter points at the right database. For example, %{asn.as_number} works with the IP to ASN Database but not with the IP to ISP Database, where the path is %{asn} .

Not supported MAP and ARRAY . The path stops at a group or a list instead of a single value. For example, %{location.country.name} needs a language at the end: %{location.country.name.en} .

getaddrinfo failed: Name or service not known . The lookup field does not hold a single IP address. This happens with X-Forwarded-For values such as 203.0.113.7, 10.0.0.1 , or with an address that includes a port. Extract the client address into its own field first.

Fluent Bit stops with Cannot open geoip2 database . The file in database does not exist or is not readable. In Docker, the path must be the path inside the container.

New data does not show up after an update. Fluent Bit still has the old file open. Reload it with SIGHUP (with hot_reload: on ) or restart it.


FAQ

No. Fluent Bit reads the database files directly. You only need an IPGeolocation.io database plan to download the files.
No. Every lookup happens on your own machine, and nothing is sent over the network.
All IP databases in MMDB format, including combined databases. The Paths in each database table above lists the paths to use with each one.
Yes. Each database covers IPv4 and IPv6, and the filter looks up both.
Daily or weekly, depending on your plan. Fluent Bit picks up a new file after a reload or restart.
Yes. Add one geoip2 filter per database file, as in the quick start, or use a combined database and read every value with one filter.

Related

Subscribe to Our Newsletter

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