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:
- Country, region, city, coordinates and time zone from the IP Geolocation Database
- VPN, proxy and Tor flags and a threat score from the IP Security Database
- The autonomous system number (ASN) and its organization from the IP to ASN Database
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
- When Fluent Bit starts, each
geoip2filter opens one MMDB file. - For each record that matches the filter, it reads the IP address from the field named in
lookup_key. - It looks the address up and, for each
recordrule, copies one value from the database into a new key. - 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:
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
| Component | Version |
|---|---|
| Fluent Bit | 5.1.3 (tested). The official container images and packages include the geoip2 filter. |
| IPGeolocation.io databases | Any IP database in MMDB format. Download them from your IPGeolocation.io account, or visit IP database pricing to choose a plan. |
| Your logs | Records 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 :
1databases/
2├── db-ip-asn.mmdb
3├── db-ip-location.mmdb
4└── db-ip-security.mmdb2. 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:
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_lines3. 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.yaml4. Step 4: Check the output
Fluent Bit prints one JSON line per record. Here it is formatted for readability:
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
| Setting | What 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:
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.
| Database | Paths |
|---|---|
| IP Geolocation Database and IP to City Database | Under location. , such as location.country.code2 , plus time_zone |
| IP to Country Database | Under location.country. , such as location.country.code2 |
| IP Security Database | At the top level, such as is_vpn and threat_score |
| IP to ASN Database | Under asn. , such as asn.as_number |
| IP to Company Database | Under company. , such as company.name.en |
| IP to ISP Database | At the top level: isp , asn , as_organization |
| IP Abuse Contact Database | Under abuse. , such as abuse.emails |
| IP WHOIS Database | Under whois. , such as whois.organization.name |
| IP to Hosting Database | At the top level: hosting_provider |
| Residential Proxy Database | At the top level: proxy_provider and last_seen |
Two details to watch with ISP data:
-
asnis 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 underlocation..
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:
| Path | Meaning |
|---|---|
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:
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 .
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):
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
| Value | Type | Example | Notes |
|---|---|---|---|
Security flags ( is_vpn , is_tor , ...) | String | "true" | Compare as strings, not booleans. |
Scores ( threat_score , confidence scores) | Integer | 50 | 0 to 100. |
| Coordinates | String | "40.78834" | Convert to numbers for geo points (below). |
| AS number | String | "9009" | No AS prefix. |
| Provider names | List | ["Private Internet Access VPN"] | Read the first entry with .0 (below). |
| Missing value | Empty 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:
1 - name: type_converter
2 match: web
3 str_key:
4 - latitude lat float
5 - longitude lon floatLists. 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
matchto 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:
1service:
2 hot_reload: onFluent 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
geoip2 filter per database file, as in the quick start, or use a combined database and read every value with one filter.Related
- IPGeolocation.io MMDB field reference
- IP Geolocation Database documentation
- IP Security Database documentation
- IP to ASN Database documentation
- IP to Company Database documentation
- Enrich logs with IPGeolocation.io in Vector
- Enrich logs with IPGeolocation.io in Grafana Alloy
- IPGeolocation.io Nginx module for MMDB databases
- All IPGeolocation.io integrations