Production Warning
If you use either CF_CONNECTING_IP or CF_IPCOUNTRY do not expect production level accuracy without making some modifications to general install instructions. Specifically if you do not want your routing to happen based on proxy IP results from Cloudflare, routing based on fake geolocations from Cloudflare and lastly storing incorrect results in a database for future processing.
Step One - Cloudflare Setup
#save in /etc/nginx/Cloudflare.conf
set_real_ip_from 103.21.244.0/22;
set_real_ip_from 103.22.200.0/22;
set_real_ip_from 103.31.4.0/22;
set_real_ip_from 104.16.0.0/12;
set_real_ip_from 108.162.192.0/18;
set_real_ip_from 131.0.72.0/22;
set_real_ip_from 141.101.64.0/18;
set_real_ip_from 162.158.0.0/15;
set_real_ip_from 172.64.0.0/13;
set_real_ip_from 173.245.48.0/20;
set_real_ip_from 188.114.96.0/20;
set_real_ip_from 190.93.240.0/20;
set_real_ip_from 197.234.240.0/22;
set_real_ip_from 198.41.128.0/17;
set_real_ip_from 2400:cb00::/32;
set_real_ip_from 2405:8100::/32;
set_real_ip_from 2405:b500::/32;
set_real_ip_from 2606:4700::/32;
set_real_ip_from 2803:f800::/32;
set_real_ip_from 2c0f:f248::/32;
set_real_ip_from 2a06:98c0::/29;
real_ip_header X-Forwarded-For;
real_ip_recursive on;
Note that we are using “X-Forwarded-For” here versus the suggested “CF-Connecting-IP” as outlined in the Cloudflare setup guide. We also add real_ip_recursive on to help with detecting the clients real ip address.
Step Two - Website’s Nginx Setup
if ($http_x_forwarded_for ~ “^([^,]+)” ) {
set $first_xff $1;
}
This captures the first element from X-Forwarded-For and gives us the clients real IP address. This is also easy to get in PHP without doing the above but if you want to utilize the real IP for geolocation checks in nginx this is critical.
Step Three - Setup Maxmind GeoLite2 Free
Firstly this is free, secondly this is extremely accurate on a country level. As an added bonus it also provides city, state, latitude, longitude, and postal with some accuracy. I typically focus on country/city so for my usage the results are pretty accurate.
Get the database files
http://geolite.maxmind.com/download/geoip/database/GeoLite2-City.tar.gz
http://geolite.maxmind.com/download/geoip/database/GeoLite2-Country.tar.gz
Next part requires a updates to nginx, specifically to install the geoip2 module. Note that I initially tried doing this by just adding the module but ended with conflicts. As a result I found that I recomplinging nginx again worked.
./configure --prefix=/etc/nginx --with-cc-opt=‘-g -O2 -fdebug-prefix-map=/build/nginx-K5HqCe/nginx-1.12.1=. -fstack-protector-strong -Wformat -Werror=format-security -fPIC -Wdate-time -D_FORTIFY_SOURCE=2’ --with-ld-opt=‘-Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fPIC’ --prefix=/usr/share/nginx --conf-path=/etc/nginx/nginx.conf --http-log-path=/var/log/nginx/access.log --error-log-path=/var/log/nginx/error.log --lock-path=/var/lock/nginx.lock --pid-path=/run/nginx.pid --modules-path=/usr/lib/nginx/modules --http-client-body-temp-path=/var/lib/nginx/body --http-fastcgi-temp-path=/var/lib/nginx/fastcgi --http-proxy-temp-path=/var/lib/nginx/proxy --http-scgi-temp-path=/var/lib/nginx/scgi --http-uwsgi-temp-path=/var/lib/nginx/uwsgi --with-debug --with-pcre-jit --with-http_ssl_module --with-http_stub_status_module --with-http_realip_module --with-http_auth_request_module --with-http_v2_module --with-http_dav_module --with-http_slice_module --with-threads --with-http_addition_module --with-http_geoip_module=dynamic --with-http_gunzip_module --with-http_gzip_static_module --with-http_sub_module --with-stream=dynamic --with-stream_ssl_module --with-mail=dynamic --with-mail_ssl_module --with-http_secure_link_module --with-http_mp4_module --add-dynamic-module=/usr/src/ngx_http_geoip2_module
Step Four - Update Nginx.conf
#Load above http
load_module modules/ngx_http_geoip2_module.so;
#Load Country and City detection GEOLITE2
These go inside http
include /etc/nginx/Cloudflare.conf; #FROM EARLIER
geoip2 /mnt/RAM_disk/GeoLite2-Country.mmdb {
$geoip2_data_country_code source=$first_xff country iso_code;
$geoip2_data_continent_code source=$first_xff continent code;
}
geoip2 /mnt/RAM_disk/GeoLite2-City.mmdb {
$geoip2_data_city_name source=$first_xff default=London city names en;
$geoip2_data_state source=$first_xff subdivisions 0 iso_code;
$geoip2_data_accuracy source=$first_xff location accuracy_radius;
$geoip2_data_lat source=$first_xff default=0 location latitude;
$geoip2_data_lng source=$first_xff default=0 location longitude;
$geoip2_data_postal source=$first_xff default=0 postal code;
}
Notice I use ram disk, while I am not sure if this helps entirely as Linux caches files.
Step Five - Update website conf
#add the if statement mentioned earlier to get the clients true IP address
if ($http_x_forwarded_for ~ "^([^,]+)" ) {
set $first_xff $1;
}
Lastly add the following where required:
fastcgi_param GEOIP_CITY_CONTINENT_CODE $geoip2_data_continent_code;
fastcgi_param GEOIP_COUNTRY_CODE $geoip2_data_country_code;
fastcgi_param GEOIP_STATE $geoip2_data_state;
fastcgi_param GEOIP_CITY $geoip2_data_city_name;
fastcgi_param GEOIP_ACCURACY $geoip2_data_accuracy;
fastcgi_param GEOIP_LATITUDE $geoip2_data_lat;
fastcgi_param GEOIP_LONGITUDE $geoip2_data_lng;
fastcgi_param GEOIP_POSTAL $geoip2_data_postal;
Restart Nginx, and run a few tests. You can access the above simply by using:
$_SERVER[‘GEOIP_CITY’].', '.$_SERVER[‘COUNTRY_CODE’];
Final Results
I have moved away from using Cloudflare’s geolocation detection and moved to a server based detection method which provides the country and city information in under 1 ms based on benchmarks. While also being accurate within the limitations of the database’s accuracy. This could be improved further by purchasing GeoIP2 databases.
Note also that you can also complete an IP lookup using php and the same database file expect lookup times to take around 300 ms give or take your setup.