** 2025-10-14 ** 792 words, 4 minutes
There are a few times when I want to check which public IP I am browsing from… I used to use the WhatsMyIP sites but the Internet being what it is these days, I switched to using my SearXNG instance which has the IP plugin enabled.
Still, there are times I need to get my IP from a script and want a dead simple option for this. So I switched to using nginx and GeoLite2.
I was looking at some Web 3.0 pretty GUI but mostly found bloated stuff made out of Go or Rust or Node.js. I finally stumbled upon ipinfo.tw which had bo…
** 2025-10-14 ** 792 words, 4 minutes
There are a few times when I want to check which public IP I am browsing from… I used to use the WhatsMyIP sites but the Internet being what it is these days, I switched to using my SearXNG instance which has the IP plugin enabled.
Still, there are times I need to get my IP from a script and want a dead simple option for this. So I switched to using nginx and GeoLite2.
I was looking at some Web 3.0 pretty GUI but mostly found bloated stuff made out of Go or Rust or Node.js. I finally stumbled upon ipinfo.tw which had both the advantage of requiring little stuff to run and providing all the informations I wanted without unnecessary extra stuff. The only drawback is that it is supposed to run using Docker… Which doesn’t match my OpenBSD requirements. But I am Docker-fluent enough to understand a Dockerfile, reverse engineer it and have it working on OpenBSD.
The overall process is that for each HTTP connection, nginx
will query the incoming IP from GeoLite2
databases, format a text HTTP response and send it back to the HTTP client.
Required packages
From a bare OpenBSD instance, one has to install the nginx
package and its nginx-geoip2
module package.
# pkg_add nginx-geoip2 nginx
Unfortunately, OpenBSD doesn’t provide the latest version of the GeoLite2
databases. Because
Last version prior to license change https://blog.maxmind.com/2019/12/significant-changes-to-accessing-and-using-geolite2-databases/ Up to date files available free of charge, but an account and EULA agreement are needed: https://dev.maxmind.com/geoip/geolite2-free-geolocation-data/
GeoLite2 databases
You can sign up for free GeoLite databases access from https://www.maxmind.com/en/geolite2/signup . For reasons, I had to use a Gmail account for it to work… When done, sign in and create License key. Write down your Account ID and License key. There seem to be an existing software, Automatic Updates for GeoIP Databases, that will update the databases. But I’d rather use my own script.
# pkg_add wget
# vi /opt/bin/update-geoip
#!/bin/ksh
LICENSE="<change_me>"
URL="https://download.maxmind.com/app/geoip_download?license_key=${LICENSE}&edition_id="
cd /var/www/cache
for DB in GeoLite2-ASN GeoLite2-City; do
doas -u www wget -q -O ${DB}.tar.gz "${URL}${DB}&suffix=tar.gz"
doas -u www tar -xzf ${DB}.tar.gz -s ':.*/::' '*.mmdb'
rm ${DB}.tar.gz
rcctl reload nginx
done
cd -
exit 0
#EOF
# chmod 0755 /opt/bin/update-geoip
# /opt/bin/update-geoip
Setting up a cron job to daily update the databases ensures I get the latest information.
nginx configuration
Stealing configuration bits from ipinfo.tw
, and adapting it to OpenBSD, the nginx
configuration to send back IP information from HTTP queries goes like this:
# vi /etc/nginx/nginx.conf
#user www;
worker_processes auto;
load_module modules/ngx_http_geoip2_module.so;
pid logs/nginx.pid;
worker_rlimit_nofile 32768;
events {
accept_mutex off;
multi_accept on;
worker_connections 4096;
}
http {
# query city databases
geoip2 /var/www/htdocs/GeoLite2-City.mmdb {
auto_reload 1d;
$ip_continent_name source=$remote_addr continent names en;
$ip_country_code source=$remote_addr country iso_code;
$ip_country_name source=$remote_addr country names en;
$ip_subdivision_name source=$remote_addr subdivisions 0 names en;
$ip_city_code source=$remote_addr postal code;
$ip_city_name source=$remote_addr city names en;
$ip_loc_lat source=$remote_addr location latitude;
$ip_loc_lon source=$remote_addr location longitude;
$ip_loc_tz source=$remote_addr location time_zone;
}
# if values are empty, deal with it for proper output
map $ip_subdivision_name $subdivision_name {
"" "n/a";
default $ip_subdivision_name;
}
map $ip_city_code $city_code {
"" "";
default "($ip_city_code)";
}
map $ip_city_name $city_name {
"" "n/a";
default $ip_city_name;
}
# query provider database
geoip2 /var/www/htdocs/GeoLite2-ASN.mmdb {
auto_reload 1d;
$ip_asn source=$remote_addr autonomous_system_number;
$ip_aso source=$remote_addr autonomous_system_organization;
$ip_as_build_epoch metadata build_epoch;
}
include mime.types;
default_type text/plain;
index index.html index.htm;
tcp_nopush on;
keepalive_timeout 60;
gzip on;
server_tokens off;
autoindex off;
client_max_body_size 512k;
keepalive_requests 100;
tcp_nodelay on;
types_hash_max_size 2048;
server {
listen 80;
listen [::]:80;
server_name ip.example.com;
root /var/www/htdocs;
charset utf-8;
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /var/www/htdocs;
}
real_ip_header X-Real-IP;
set_real_ip_from 10.0.0.0/8;
set_real_ip_from 172.16.0.0/12;
set_real_ip_from 192.168.0.0/16;
add_header X-Powered-By "Inspired by ipinfo.tw/github" always;
add_header Cache-Control "no-store" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "no-referrer-when-downgrade" always;
add_header Content-Security-Policy "default-src 'none'; img-src 'self'" always;
location = / {
return 200 "Public IP: $remote_addr
---
Continent: $ip_continent_name
Country: $ip_country_name ($ip_country_code)
Region: $subdivision_name
City: $city_name $city_code [$ip_loc_lat/$ip_loc_lon]
---
Provider: $ip_aso (AS$ip_asn)
---
Timezone: $ip_loc_tz
User-Agent: $http_user_agent";
}
location = /ip { return 200 "$remote_addr\n"; }
location = /country { return 200 "$ip_country_name\n"; }
location = /json {
default_type application/json;
return 200 "{\"ip\":\"$remote_addr\",\"continent_name\":\"$ip_continent_name\",\"country_name\":\"$ip_country_name\",\"country_code\":\"$ip_country_code\",\"region\":\"$subdivision_name\",\"city\":\"$city_name\",\"city_code\":\"$city_code\",\"latitude\":\"$ip_loc_lat\",\"longitude\":\"$ip_loc_lon\",\"provider\":\"$ip_aso\",\"asn\":\"$ip_asn\",\"timezone\":\"$ip_loc_tz\",\"user-agent\":\"$http_user_agent\"}";
}
}
}
# nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
# rcctl enable nginx
# rcctl start nginx
Usage
Browse to the published URL using a Web browser or a command line to get the results.
# curl http://example.com
Public IP: 192.0.2.144
---
Continent: Europe
Country: France (FR)
Region: Île-de-France
City: Paris (75001) [48.864716/2.349014]
---
Provider: Orange (AS3215)
---
Timezone: Europe/Paris
User-Agent: curl/8.14.0
# curl http://example.com/json
{"ip":"192.0.2.144","continent_name":"Europe","country_name":"France","country_code":"FR","region":"Île-de-France","city":"Paris","city_code":"(75001)","latitude":"48.864716","longitude":"2.349014","provider":"Orange","asn":"3215","timezone":"Europe/Paris","user-agent":"curl/8.14.0"}
For those of you that trust people on the Internet, I’ve configured such service on noGoo.me. You can use https://ip.nogoo.me if you like to.