Adding country, region, and coordinate data to IP-based events makes traffic logs easier to route, aggregate, and map. A GeoIP-enriched event lets you separate traffic by geography, spot unexpected source regions, and hand downstream tools data that is ready for location-aware searches and dashboards.
The geoip filter reads an IP field, looks it up in the bundled MaxMind GeoLite2 database by default, and adds the result back into the event. In current Logstash pipelines, the cleanest ECS-compatible pattern is to parse the address into [client][ip] and let geoip write the enrichment into [client][geo], including [client][geo][location] for geospatial queries.
Private, loopback, and other non-routable addresses normally return no match, and a stale downloaded EULA database can tag events with _geoip_expired_database instead of adding geo fields. If you later ship enriched events to Elasticsearch with a custom target or index template, make sure the final location field still maps as a geo_point before relying on map visualizations.
Steps to use the Logstash geoip filter:
- Create a sample log file that the logstash user can read.
$ sudo install --directory --owner=logstash --group=logstash /var/lib/logstash/examples $ printf '%s\n' '8.8.8.8 - - [07/Apr/2026:08:32:15 +0000] "GET /geoip-test HTTP/1.1" 200 123 "-" "curl/8.7.1"' | sudo tee /var/lib/logstash/examples/geoip.log >/dev/null
Use a public IP address for testing; RFC1918, loopback, and link-local ranges usually do not produce GeoIP data.
- Create a temporary pipeline configuration at /etc/logstash/conf.d/70-geoip.conf.
input { file { path => "/var/lib/logstash/examples/geoip.log" mode => "read" exit_after_read => true sincedb_path => "/tmp/logstash-geoip.sincedb" } } filter { grok { id => "extract_client_ip" match => { "message" => "%{IP:[client][ip]}" } tag_on_failure => ["_no_client_ip"] } if [client][ip] { geoip { id => "geoip_client" source => "[client][ip]" ecs_compatibility => "v8" } } } output { stdout { codec => rubydebug { metadata => false } } }Parsing directly into [client][ip] lets the current GeoIP plugin use its ECS-aware default target, so the enrichment lands under [client][geo] instead of the older top-level geoip field.
If you keep ECS compatibility enabled but your source field is not an ip sub-field such as [client][ip] or [source][ip], set an explicit target or the filter will not know where to place the geo fields.
- Test the pipeline configuration before running it.
$ sudo -u logstash /usr/share/logstash/bin/logstash --path.settings /etc/logstash --path.data /tmp/logstash-configtest --config.test_and_exit -f /etc/logstash/conf.d/70-geoip.conf Using bundled JDK: /usr/share/logstash/jdk ##### snipped ##### [2026-04-07T08:38:08,691][INFO ][logstash.javapipeline ][main] Pipeline `main` is configured with `pipeline.ecs_compatibility: v8` setting. All plugins in this pipeline will default to `ecs_compatibility => v8` unless explicitly configured otherwise. Configuration OK [2026-04-07T08:38:08,693][INFO ][logstash.runner ] Using config.test_and_exit mode. Config Validation Result: OK. Exiting Logstash
- Run the pipeline once against the sample file.
$ sudo -u logstash /usr/share/logstash/bin/logstash --path.settings /etc/logstash --path.data /tmp/logstash-geoip-run -f /etc/logstash/conf.d/70-geoip.conf Using bundled JDK: /usr/share/logstash/jdk { "@timestamp" => 2026-04-07T08:39:48.236562880Z, "client" => { "geo" => { "country_iso_code" => "US", "country_name" => "United States", "timezone" => "America/Chicago", "location" => { "lat" => 37.751, "lon" => -97.822 } }, "ip" => "8.8.8.8" }, "message" => "8.8.8.8 - - [07/Apr/2026:08:32:15 +0000] \"GET /geoip-test HTTP/1.1\" 200 123 \"-\" \"curl/8.7.1\"" }Because mode => "read" and exit_after_read => true are set, the one-shot test exits after reading the sample file instead of tailing it indefinitely.
If the output shows _geoip_lookup_failure, confirm that grok extracted the IP and that the address is public and routable.
- Replace the temporary stdout output with your real destination after the event shape looks correct.
output { elasticsearch { hosts => ["http://elasticsearch.example.net:9200"] ilm_enabled => false index => "logstash-geoip-%{+YYYY.MM.dd}" } }When you keep the default ECS target, the elasticsearch output templates map [client][geo][location] as a geo_point. If you change target or use a custom index template, update the mapping so the final location field still uses geo_point.
Setting ilm_enabled => false keeps the explicit daily index name in effect. When the cluster uses HTTPS or a private CA, add the current elasticsearch output TLS settings rather than relying on older keys such as cacert.
- Remove the one-shot file input settings when converting the example into a long-running service pipeline.
mode => "read" exit_after_read => true sincedb_path => "/tmp/logstash-geoip.sincedb"
For a persistent file input, switch back to tailing behavior and use a writable dedicated sincedb file under /var/lib/logstash or the default <path.data>/plugins/inputs/file location.
Mohd Shakir Zakaria is a cloud architect with deep roots in software development and open-source advocacy. Certified in AWS, Red Hat, VMware, ITIL, and Linux, he specializes in designing and managing robust cloud and on-premises infrastructures.
