
Intro #
Fluentd is an open-source data collector used to collect, parse, transform, enrich, and route logs from different sources to different destinations. Instead of leaving logs as plain text files scattered across servers, Fluentd can turn them into structured events that are easier to search, analyze, and forward to storage systems.
A Fluentd pipeline usually has three major parts:
- Input – where Fluentd reads data from, such as a log file.
- Filter – where Fluentd modifies or enriches the event.
- Output – where Fluentd sends the processed event, such as stdout, a file, Elasticsearch, OpenSearch, S3, or another logging system.
In this tutorial, we will use Fluentd to read an Nginx access log, parse each request, extract the visitor IP address, query the IP2Location DB26 IPv6 BIN database, and add geolocation and network information to the log record.
The final enriched log record will include the original Nginx fields, such as request path, status code, user agent, and referer, plus IP2Location fields such as country, region, city, latitude, longitude, ISP, domain, usage type, address type, district, ASN, and AS name.
This setup uses a single Debian 13 server running both Nginx and Fluentd.
What We Will Build
The data flow will look like this:

Prerequisites #
You need:
A Debian 13 server.
Root or sudo access.
Nginx installed on the same server.
Port 80 open to the internet if you want to receive real external hits.
An IP2Location DB26 BIN file.
The fluent-plugin-ip2location Fluentd filter plugin.
For DB26, the BIN file name may look similar to this:
IP-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ZIPCODE-TIMEZONE-ISP-DOMAIN-NETSPEED-AREACODE-WEATHER-MOBILE-ELEVATION-USAGETYPE-ADDRESSTYPE-CATEGORY-DISTRICT-ASN.BIN
For IPv6 support, the file name may look similar to this:
IPV6-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ZIPCODE-TIMEZONE-ISP-DOMAIN-NETSPEED-AREACODE-WEATHER-MOBILE-ELEVATION-USAGETYPE-ADDRESSTYPE-CATEGORY-DISTRICT-ASN.BIN
In this tutorial, we will rename the BIN file to:
/opt/ip2location/DB26.BIN
This keeps the Fluentd configuration shorter and easier to read.
Step 1: Update Debian and Install Basic Packages #
Run:
sudo apt update
sudo apt install -y curl ca-certificates gnupg unzip nginx jq
Enable and start Nginx:
sudo systemctl enable --now nginx
sudo systemctl status nginx
Create a simple test page:
echo "IP2Location Fluentd Nginx Demo" | sudo tee /var/www/html/index.html
Test locally:
curl -I http://127.0.0.1/
You should see an HTTP response from Nginx.
Step 2: Open HTTP Access #
As we want to geolocate actual visitors to the website, make sure your firewall allows incoming connections to port 80.
Then, visit the website from another machine in a browser:
http://<YOUR_SERVER_PUBLIC_IP>/

Nginx should write requests to:
/var/log/nginx/access.log
Check the log:
tail /var/log/nginx/access.log

Step 3: Install Fluent Package v6 #
Install Fluentd using the official Fluent Package v6 LTS Debian Trixie installer:
curl -fsSL https://fluentd.cdn.cncf.io/sh/install-debian-trixie-fluent-package6-lts.sh | sudo sh
Enable and start Fluentd:
sudo systemctl enable --now fluentd
sudo systemctl status fluentd
Check the Fluentd version:
/opt/fluent/bin/fluentd –version

Check the Fluentd log:
sudo tail -n 50 /var/log/fluent/fluentd.log

Step 4: Install the IP2Location Fluentd Plugin #
Install the plugin into the Ruby environment used by Fluentd:
sudo fluent-gem install fluent-plugin-ip2location

Confirm it is installed:
sudo fluent-gem list fluent-plugin-ip2location
You should see fluent-plugin-ip2location in the gem list.

Step 5: Install the IP2Location BIN File #
You use either register for the IP2Location LITE BIN database or subscribe to the commercial IP2Location BIN database. Then, download the zipped file containing the BIN you want and extract the BIN file. In our case, we’re using the DB26 IPv6 BIN file. Upload the BIN file into your home directory on the server. Rename it to DB26.BIN to make it easier to write the commands below.
Create a directory for the IP2Location database:
sudo mkdir -p /opt/ip2location
sudo mv ~/DB26.BIN /opt/ip2location/DB26.BIN
sudo chown root:root /opt/ip2location/DB26.BIN
sudo chmod 0644 /opt/ip2location/DB26.BIN
sudo chmod 0755 /opt/ip2location
Confirm the file exists:
ls -lh /opt/ip2location/DB26.BIN

The Fluentd process must be able to read this file.
Step 6: Allow Fluentd to Read the Nginx Access Log #
On Debian-based systems, /var/log/nginx/access.log may only be readable by root or members of the adm group.
Check the permission:
ls -l /var/log/nginx/access.log
Check which user Fluentd runs as:
systemctl show fluentd -p User -p Group

If the service has a dedicated user such as _fluentd, add it to the adm group:
sudo usermod -aG adm _fluentd
sudo systemctl restart fluentd
If your Fluentd service runs as another user, replace _fluentd with that user.
Confirm access:
sudo -u _fluentd test -r /var/log/nginx/access.log && echo "Fluentd can read nginx access.log"
If the command says the file is readable, continue.

If your Fluentd service runs as root, this permission step is usually not required.
Step 7: Configure Fluentd to Read and Enrich Nginx Access Logs #
Create a Fluentd configuration file:
sudo nano /etc/fluent/conf.d/nginx-ip2location.conf
Paste the following configuration:
<source>
@type tail
path /var/log/nginx/access.log
pos_file /var/log/fluent/nginx-access.pos
tag nginx.access
read_from_head false
<parse>
@type nginx
keep_time_key true
</parse>
</source>
<filter nginx.access>
@type ip2location
database /opt/ip2location/DB26.BIN
ip_field remote
output_field ip2location
fields country_short,country_long,region,city,isp,latitude,longitude,domain,zipcode,timezone,netspeed,iddcode,areacode,weatherstationcode,weatherstationname,mcc,mnc,mobilebrand,elevation,usagetype,addresstype,category,district,asn,as,as_domain,as_usagetype,as_cidr
skip_private_ip true
skip_invalid_ip true
include_unknown false
cache_size 10000
on_error warn
</filter>
<match nginx.access>
@type stdout
</match>
Save and exit.
This configuration does three things:
- Reads
/var/log/nginx/access.log. - Parses Nginx access log lines into structured fields.
- Enriches the record using the IP address in the remote field.
The output is sent to Fluentd stdout, which is useful for testing. Since Fluentd is running as a systemd service, the output will appear in:
/var/log/fluent/fluentd.log
Step 8: Validate the Fluentd Configuration #
Run a dry-run configuration check:
sudo /opt/fluent/bin/fluentd --dry-run -c /etc/fluent/fluentd.conf
If the configuration is valid, restart Fluentd:
sudo systemctl restart fluentd
sudo systemctl status fluentd
Watch the Fluentd log:
sudo tail -f /var/log/fluent/fluentd.log
Keep this terminal open while you generate test traffic.
Step 9: Generate Test Log Entries #
To quickly generate a few log entries, we used https://globalping.io/ to conduct HTTP tests using several random IP addresses on our website http://<YOUR_SERVER_PUBLIC_IP>/.
You should see a few enriched records containing the ip2location object similar to the below:
{
"remote": "54.38.94.224",
"host": "-",
"user": "-",
"time": "25/Jun/2026:06:22:37 +0000",
"method": "GET",
"path": "/",
"code": "200",
"size": "62",
"referer": "-",
"agent": "globalping probe (https://github.com/jsdelivr/globalping)",
"ip2location": {
"country_short": "FR",
"country_long": "France",
"region": "Hauts-de-France",
"city": "Roubaix",
"isp": "OVH SAS",
"latitude": "50.69371032714844",
"longitude": "3.174438953399658",
"domain": "ovh.com",
"zipcode": "59100",
"timezone": "+02:00",
"netspeed": "T1",
"iddcode": "33",
"areacode": "03",
"weatherstationcode": "FRXX0084",
"weatherstationname": "Roubaix",
"elevation": "32",
"usagetype": "DCH",
"addresstype": "U",
"category": "IAB19-11",
"district": "North",
"asn": "16276",
"as": "OVH SAS",
"as_domain": "ovh.com",
"as_usagetype": "DCH",
"as_cidr": "54.38.0.0/16"
}
}
The exact result depends on your DB26 BIN version and which fields you’ve selected for output.

Step 10: Optional – Write Enriched Records to a JSON File #
For debugging, stdout is simple. For a more practical setup, you may want to write enriched records to a JSON log file.
Let’s modify the configuration file again.
sudo nano /etc/fluent/conf.d/nginx-ip2location.conf
Replace this section:
<match nginx.access>
@type stdout
</match>
With this:
<match nginx.access>
@type file
path /var/log/fluent/nginx-ip2location
append true
<format>
@type json
</format>
<buffer time>
timekey 60
timekey_wait 10s
flush_interval 5s
</buffer>
</match>
Restart Fluentd:
sudo systemctl restart fluentd
Generate traffic again, then check the output files:
sudo find /var/log/fluent -name 'nginx-ip2location*' -type f -print
View the JSON records:
sudo find /var/log/fluent -name 'nginx-ip2location*' -type f -exec tail -n 20 {} \;
Each line should be a JSON object containing the parsed Nginx fields and the nested ip2location enrichment.

Step 11: Optional – Merge IP2Location Fields into the Root Record #
By default, this tutorial stores enrichment data under a nested object like this:
{
"remote": "54.38.94.224",
"host": "-",
"user": "-",
"time": "25/Jun/2026:06:22:37 +0000",
"method": "GET",
"path": "/",
"code": "200",
"size": "62",
"referer": "-",
"agent": "globalping probe (https://github.com/jsdelivr/globalping)",
"ip2location": {
"country_short": "FR",
"country_long": "France",
"region": "Hauts-de-France",
"city": "Roubaix",
"isp": "OVH SAS",
"latitude": "50.69371032714844",
"longitude": "3.174438953399658",
"domain": "ovh.com",
"zipcode": "59100",
"timezone": "+02:00",
"netspeed": "T1",
"iddcode": "33",
"areacode": "03",
"weatherstationcode": "FRXX0084",
"weatherstationname": "Roubaix",
"elevation": "32",
"usagetype": "DCH",
"addresstype": "U",
"category": "IAB19-11",
"district": "North",
"asn": "16276",
"as": "OVH SAS",
"as_domain": "ovh.com",
"as_usagetype": "DCH",
"as_cidr": "54.38.0.0/16"
}
}
If you prefer flat fields, change the filter section to use merge_record true.
Replace your existing IP2Location filter with this:
<filter nginx.access>
@type ip2location
database /opt/ip2location/DB26.BIN
ip_field remote
merge_record true
prefix ip2location_
fields country_short,country_long,region,city,isp,latitude,longitude,domain,zipcode,timezone,netspeed,iddcode,areacode,weatherstationcode,weatherstationname,mcc,mnc,mobilebrand,elevation,usagetype,addresstype,category,district,asn,as,as_domain,as_usagetype,as_cidr
skip_private_ip true
skip_invalid_ip true
include_unknown false
cache_size 10000
on_error warn
</filter>
The output will look like this:
{
"remote": "88.198.99.234",
"host": "-",
"user": "-",
"time": "25/Jun/2026:07:05:34 +0000",
"method": "GET",
"path": "/",
"code": "200",
"size": "62",
"referer": "-",
"agent": "globalping probe (https://github.com/jsdelivr/globalping)",
"ip2location_country_short": "DE",
"ip2location_country_long": "Germany",
"ip2location_region": "Sachsen",
"ip2location_city": "Falkenstein",
"ip2location_isp": "Hetzner Online GmbH",
"ip2location_latitude": "50.47785186767578",
"ip2location_longitude": "12.371562957763672",
"ip2location_domain": "hetzner.de",
"ip2location_zipcode": "08223",
"ip2location_timezone": "+02:00",
"ip2location_netspeed": "T1",
"ip2location_iddcode": "49",
"ip2location_areacode": "03745",
"ip2location_weatherstationcode": "GMXX0261",
"ip2location_weatherstationname": "Plauen",
"ip2location_elevation": "565",
"ip2location_usagetype": "DCH",
"ip2location_addresstype": "U",
"ip2location_category": "IAB19-11",
"ip2location_district": "Vogtlandkreis",
"ip2location_asn": "24940",
"ip2location_as": "Hetzner Online GmbH",
"ip2location_as_domain": "hetzner.de",
"ip2location_as_usagetype": "DCH",
"ip2location_as_cidr": "88.198.0.0/16"
}

This format can be convenient if your destination system prefers flat fields.
Conclusion #
Hope you’ve found our simple tutorial for configuring the IP2Location Fluentd Filter Plugin easy enough to understand. It should be relatively straightforward to adapt what we’ve shown above into a production scenario.
Explore more integration guides and data enrichment tips
