CloudFlare and logging visitor IP addresses via in PHP

PhpLoggingIp AddressCloudflare

Php Problem Overview


I'm trying to track and log users/visitors that are accessing my website using PHP's $_SERVER['REMOTE_ADDR'] to do so. A typical method for IP address tracking in PHP.

However, I am using CloudFlare for caching and such and receiving their IP addresses as CloudFlare's: >108.162.212.* - 108.162.239.*

What would be a correct method of retrieving the actual users/visitors IP address while still using CloudFlare?

Php Solutions


Solution 1 - Php

Extra server variables that are available to cloud flare are:

$_SERVER["HTTP_CF_CONNECTING_IP"] real visitor ip address, this is what you want

$_SERVER["HTTP_CF_IPCOUNTRY"] country of visitor

$_SERVER["HTTP_CF_RAY"]

$_SERVER["HTTP_CF_VISITOR"] this can help you know if its http or https

you can use it like this:

if (isset($_SERVER["HTTP_CF_CONNECTING_IP"])) {
  $_SERVER['REMOTE_ADDR'] = $_SERVER["HTTP_CF_CONNECTING_IP"];
}

If you do this, and the validity of the visiting IP address is important, you might need to verify that the $_SERVER["REMOTE_ADDR"] contains an actual valid cloudflare IP address, because anyone can fake the header if he was able to connect directly to the server IP.

Solution 2 - Php

Update: CloudFlare has released a module mod_cloudflare for apache, the module will log and display the actual visitor IP Addresses rather than those accessed by cloudflare! https://www.cloudflare.com/resources-downloads#mod_cloudflare (Answer by: olimortimer)

If you dont have access to the apache runtime you can use the script below, this will allow you to check if the connection was through cloudflare and get the users ip.

I am rewriting my answer i used for another question "CloudFlare DNS / direct IP identifier"

Cloudflare's ips are stored in public so you can go view them here then check if the ip is from cloudflare (this will allow us to get the real ip from the http header HTTP_CF_CONNECTING_IP).

If you are using this to disable all non cf connections or vice versa, i recommend you to have a single php script file that gets called before every other script such as a common.php or pagestart.php etc.

function ip_in_range($ip, $range) {
	if (strpos($range, '/') == false)
		$range .= '/32';

	// $range is in IP/CIDR format eg 127.0.0.1/24
	list($range, $netmask) = explode('/', $range, 2);
	$range_decimal = ip2long($range);
	$ip_decimal = ip2long($ip);
	$wildcard_decimal = pow(2, (32 - $netmask)) - 1;
	$netmask_decimal = ~ $wildcard_decimal;
	return (($ip_decimal & $netmask_decimal) == ($range_decimal & $netmask_decimal));
}

function _cloudflare_CheckIP($ip) {
	$cf_ips = array(
		'199.27.128.0/21',
		'173.245.48.0/20',
		'103.21.244.0/22',
		'103.22.200.0/22',
		'103.31.4.0/22',
		'141.101.64.0/18',
		'108.162.192.0/18',
		'190.93.240.0/20',
		'188.114.96.0/20',
		'197.234.240.0/22',
		'198.41.128.0/17',
		'162.158.0.0/15',
		'104.16.0.0/12',
	);
	$is_cf_ip = false;
	foreach ($cf_ips as $cf_ip) {
		if (ip_in_range($ip, $cf_ip)) {
			$is_cf_ip = true;
			break;
		}
	} return $is_cf_ip;
}

function _cloudflare_Requests_Check() {
	$flag = true;

	if(!isset($_SERVER['HTTP_CF_CONNECTING_IP'])) 	$flag = false;
	if(!isset($_SERVER['HTTP_CF_IPCOUNTRY'])) 		$flag = false;
	if(!isset($_SERVER['HTTP_CF_RAY'])) 			$flag = false;
	if(!isset($_SERVER['HTTP_CF_VISITOR'])) 		$flag = false;
	return $flag;
}

function isCloudflare() {
	$ipCheck 		= _cloudflare_CheckIP($_SERVER['REMOTE_ADDR']);
	$requestCheck 	= _cloudflare_Requests_Check();
	return ($ipCheck && $requestCheck);
}

// Use when handling ip's
function getRequestIP() {
	$check = isCloudflare();

	if($check) {
		return $_SERVER['HTTP_CF_CONNECTING_IP'];
	} else {
		return $_SERVER['REMOTE_ADDR'];
	}
}

To use the script it's quite simple:

$ip = getRequestIP();
$cf = isCloudflare();

if($cf) echo "Connection is through cloudflare: <br>";
else    echo "Connection is not through cloudflare: <br>";

echo "Your actual ip address is: ". $ip;
echo "The server remote address header is: ". $_SERVER['REMOTE_ADDR'];

This script will show you the real ip address and if the request is CF or not!

Solution 3 - Php

Since this question was asked and answered, CloudFlare has released mod_cloudflare for Apache, which logs & displays the actual visitor IP address rather than the CloudFlare address:

https://www.cloudflare.com/resources-downloads#mod_cloudflare

Solution 4 - Php

Cloudflare sends some additional request headers to your server including CF-Connecting-IP which we can store into $user_ip, if defined, using this simple one-liner:

$user_ip = (isset($_SERVER["HTTP_CF_CONNECTING_IP"])?$_SERVER["HTTP_CF_CONNECTING_IP"]:$_SERVER['REMOTE_ADDR']);

Solution 5 - Php

It would be hard to convert HTTP_CF_CONNECTING_IP to REMOTE_ADDR. So you can use apache (.htaccess) auto prepending to do that. So that you do not need to think about whether the $_SERVER['REMOTE_ADDR'] has the correct value in all the PHP scripts.

.htaccess code

php_value auto_prepend_file "/path/to/file.php"

php code (file.php)

<?php

define('CLIENT_IP', isset($_SERVER['HTTP_CF_CONNECTING_IP']) ? $_SERVER['HTTP_CF_CONNECTING_IP'] : $_SERVER['REMOTE_ADDR']);

Learn More here

Solution 6 - Php

HTTP_CF_CONNECTING_IP is only working if you are using cloudflare maybe you transfer your site or remove cloudflare you will forget the value so use this code .

$ip=$_SERVER["HTTP_CF_CONNECTING_IP"];
if (!isset($ip)) {
  $ip = $_SERVER['REMOTE_ADDR'];
}

Solution 7 - Php

In Laravel add the following line to AppServiceProvider:

...
use Symfony\Component\HttpFoundation\Request;


...
public function boot()
{
    Request::setTrustedProxies(['REMOTE_ADDR'], Request::HEADER_X_FORWARDED_FOR);
}

Now you can get real client IP using request()->ip().

Read more here.

Solution 8 - Php

When you are using CloudFlare all your requests between your server and users are routed through CloudFlare servers.

In this case there are two methods to get User's Real IP Address:

  1. Through Extra Server Headers added by CloudFlare Servers
  2. Adding a CloudFlare Apache/NGINX Module on your server.

Method 1: Get IP though extra Server Headers

You can use the following code to get user's IP Address:

$user_ip = (isset($_SERVER["HTTP_CF_CONNECTING_IP"]) $_SERVER["HTTP_CF_CONNECTING_IP"]:$_SERVER['REMOTE_ADDR']);

How it is working?

CloudFlare adds some extra server variables in the request as follows:

$_SERVER["HTTP_CF_CONNECTING_IP"] - Real IP Address of user

$_SERVER["HTTP_CF_IPCOUNTRY"] - ISO2 Country of the User

$_SERVER["HTTP_CF_RAY"] A Special string for loggin purpose

In the above code, we are checking if $_SERVER["HTTP_CF_CONNECTING_IP"] is set or not. If it is there we will consider that as user's IP Address else we will use the default code as $_SERVER['REMOTE_ADDR']

Method 2: Installing Cloudflare Module on your server

Solution 9 - Php

Cloudflare has an option to use a "Pseudo IPv4" address in the headers on the Network Management page for the domain. You have the option of adding or overwriting the headers that are sent to your server.

Cloudflare Dashboard | Network

From the documentation:

> What is Pseudo IPv4? > > As a stopgap to accelerate the adoption of IPv6, Cloudflare offers Pseudo IPv4 which supports IPv6 addresses in legacy applications expecting IPv4 addresses. The goal is to provide a nearly unique IPv4 address for each IPv6 address, using Class E IPv4 address space, which is designated as experimental and would not normally see traffic. To learn more see here. > > Options > * Add header: Add additional Cf-Pseudo-IPv4 header only > * Overwrite headers: Overwrite the existing Cf-Connecting-IP and X-Forwarded-For headers with a pseudo IPv4 address. > > Note: We recommend leaving this set to “Off” unless you have a specific need.

You can learn more about this feature from this article from Cloudflare, where it goes into a little more detail about how they try to accommodate the 128-bit address space of IPv6 in the 32-bit space found in IPv4.

In the event that you would like to know the IPv6 address along with the pseudo IPv4, Cloudflare adds a Cf-Connecting-IPv6 header when Pseudo IPv4 is enabled. This can be used to measure how much of your traffic is originating from devices that are on IPv6 networks, which can be useful if you need a solid number to show management before investments are made in updating systems that are still bound to the IPv4 limitations.

Solution 10 - Php

For magento 1.x users (I haven't try magento 2.0 yet), check https://tall-paul.co.uk/2012/03/13/magento-show-remote-ip-in-cloudflare-the-right-way/ which needs to change app/etc/local.xml and add: HTTP_CF_CONNECTING_IP

Solution 11 - Php

It's better to check cloudflare ip ranges online (because it may change anytime) then check it's from cloudflare or not.

Cloudflare IP txt

If the source is Cloudflare you can use $_SERVER['HTTP_CF_CONNECTING_IP'] to get your client request ip-address but it's not safe to use it for all requests because it can send by any user in request header to trick you.

You can use following code to get real ip-address of your client request:

function _getUserRealIP() {
    $ipaddress = '';
    if(isset($_SERVER['REMOTE_ADDR']))
        $ipaddress = $_SERVER['REMOTE_ADDR'];
    else if (isset($_SERVER['HTTP_CLIENT_IP']))
        $ipaddress = $_SERVER['HTTP_CLIENT_IP'];
    else if(isset($_SERVER['HTTP_X_FORWARDED_FOR']))
        $ipaddress = $_SERVER['HTTP_X_FORWARDED_FOR'];
    else if(isset($_SERVER['HTTP_X_FORWARDED']))
        $ipaddress = $_SERVER['HTTP_X_FORWARDED'];
    else if(isset($_SERVER['HTTP_X_CLUSTER_CLIENT_IP']))
        $ipaddress = $_SERVER['HTTP_X_CLUSTER_CLIENT_IP'];
    else if(isset($_SERVER['HTTP_FORWARDED_FOR']))
        $ipaddress = $_SERVER['HTTP_FORWARDED_FOR'];
    else if(isset($_SERVER['HTTP_FORWARDED']))
        $ipaddress = $_SERVER['HTTP_FORWARDED'];
    else
        $ipaddress = 'UNKNOWN';
    return $ipaddress;
}

function _readCloudflareIps()
{
    $file = file("https://www.cloudflare.com/ips-v4",FILE_IGNORE_NEW_LINES);
    return $file;
}

function _checkIpInRange($ip, $range) {
    if (strpos($range, '/') == false)
        $range .= '/32';

    // $range is in IP/CIDR format eg 127.0.0.1/24
    list($range, $netmask) = explode('/', $range, 2);
    $range_decimal = ip2long($range);
    $ip_decimal = ip2long($ip);
    $wildcard_decimal = pow(2, (32 - $netmask)) - 1;
    $netmask_decimal = ~ $wildcard_decimal;
    return (($ip_decimal & $netmask_decimal) == ($range_decimal & $netmask_decimal));
}

function _checkIsCloudflare($ip) {
    $cf_ips = _readCloudflareIps();
    $is_cf_ip = false;
    foreach ($cf_ips as $cf_ip) {
        if (_checkIpInRange($ip, $cf_ip)) {
            $is_cf_ip = true;
            break;
        }
    }
    return $is_cf_ip;
}

function getRealIp()
{
    $httpIp = _getUserRealIP();
    $check = _checkIsCloudflare($httpIp);
    if ($check) {
        return $_SERVER['HTTP_CF_CONNECTING_IP'];
    }else{
        return $httpIp;
    }
}

After importing this functions to your code you just need to call getRealIp() function like:

$userIp = getRealIp();
echo $userIp();

Solution 12 - Php

another way to get it in Laravel is simply by reading HTTP_CF_CONNECTING_IP header

$request->server('HTTP_CF_CONNECTING_IP')

you can read more about it here: How to get real client IP behind Cloudflare in Laravel / PHP

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestiontfontView Question on Stackoverflow
Solution 1 - Phpsharp12345View Answer on Stackoverflow
Solution 2 - PhpCallumView Answer on Stackoverflow
Solution 3 - PhpolimortimerView Answer on Stackoverflow
Solution 4 - PhpSainanView Answer on Stackoverflow
Solution 5 - PhpSupun KavindaView Answer on Stackoverflow
Solution 6 - PhpRootToolsView Answer on Stackoverflow
Solution 7 - PhpKhalil LalehView Answer on Stackoverflow
Solution 8 - PhpAshutosh KumarView Answer on Stackoverflow
Solution 9 - PhpmatigoView Answer on Stackoverflow
Solution 10 - PhpxinqiuView Answer on Stackoverflow
Solution 11 - PhpMajid JalilianView Answer on Stackoverflow
Solution 12 - PhpIgor SimicView Answer on Stackoverflow