112

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?

Marcus Adams
  • 53,009
  • 9
  • 91
  • 143
tfont
  • 10,891
  • 7
  • 56
  • 52

13 Answers13

313

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.

William Desportes
  • 1,412
  • 1
  • 22
  • 31
sharp12345
  • 4,420
  • 3
  • 22
  • 38
  • 12
    If Security matters one _should_ check if the request comes from a Cloudflare IP Range ( https://www.cloudflare.com/ips ). Like, for example in this Joomla! Plugin : https://github.com/piccaso/joomla-cf/blob/master/cf/cf.php – Florian F Mar 25 '15 at 21:25
  • Cloudflare also have this code listed on their site. https://support.cloudflare.com/hc/en-us/articles/200170876-I-can-t-install-mod-cloudflare-and-there-s-no-plugin-to-restore-original-visitor-IP-What-should-I-do- – C0nw0nk Jun 30 '15 at 11:57
  • http_cf_connecting_ip sometimes gives things like: 2a01:6500:a043:33a6:b36a:7259:4de5:2500 any idea why? – michael Jul 05 '18 at 06:03
  • 1
    @michael thats because its returning your ipv6. – Lewis Browne Aug 15 '19 at 13:30
  • The `$_SERVER["HTTP_X_FORWARDED_FOR"]` also availabe, but **it is not reliable** always –  Nov 04 '19 at 11:36
  • HTTP_CF_CONNECTING_IP sometimes gives IPv6, is there any solution to get only IPv4? I saw something called `Pseudo IPv4` in Cloudflare. – CHOO YJ Jul 15 '21 at 15:20
  • It is worth adding that you may need to enable IP Geolocation in the Cloudflare Dashboard through the Network app. Source : https://support.cloudflare.com/hc/en-us/articles/200168236-Configuring-Cloudflare-IP-Geolocation – Jeff H Jan 02 '22 at 17:03
19

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!

Callum
  • 725
  • 6
  • 17
  • 1
    This is the correct answer, however, keep in mind that you need to keep updating the IPs regularly. The current ones in the code above are already invalid and therefore the code will fail. You can get the latest IPs here: https://www.cloudflare.com/ips/ Ideally, you should store these in a file so that you only have to update the external file or even better, periodically fetch them from here: https://www.cloudflare.com/ips-v4 – rafark Jul 07 '20 at 17:43
14

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 = $_SERVER["HTTP_CF_CONNECTING_IP"] ?? $_SERVER['REMOTE_ADDR'];
Skylar Ittner
  • 802
  • 11
  • 26
Sainan
  • 1,274
  • 2
  • 19
  • 32
  • 1
    The problem with this is that that's just a simple header, so anyone can just set it to whatever IP they want which is a huge security vulnerability. – rafark Jun 10 '20 at 06:16
  • 1
    @rafark Not if your server is properly configured and behind Cloudflare. – Sainan Jun 10 '20 at 17:17
  • Yes, of course. This is a good solution per se but checks should still be made before using HTTP_CF_CONNECTING_IP, like in [Callum's answer](https://stackoverflow.com/a/38298029/2260096) – rafark Jun 11 '20 at 17:58
  • @rafark I also agree but that's an awful lot of PHP code which is already present in another form in your Apache config (I hope) and this code is more to allow for easy debugging and if an attacker has access to your debugging environment I think you have more important problems to deal with. – Sainan Jun 12 '20 at 06:06
12

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

sjagr
  • 15,983
  • 5
  • 40
  • 67
olimortimer
  • 1,373
  • 10
  • 23
  • 2
    2019 - They no longer support `mod_cloudflare`, yet it can be used. There is something new called `mod_remoteip`. Reference: https://support.cloudflare.com/hc/en-us/articles/200170786-Restoring-original-visitor-IPs-Logging-visitor-IP-addresses-with-mod-cloudflare- – sinaza Nov 21 '19 at 16:52
  • @sinaza probably best to create a new answer, as my answer for `mod_cloudflare` was from 4 years ago – olimortimer Jan 18 '20 at 08:25
  • @sjagr in regards to your edits, they are incorrect - CloudFlare is actually Cloudflare, as I originally posted – olimortimer Jan 18 '20 at 08:26
3

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

Supun Kavinda
  • 1,355
  • 14
  • 13
2

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'];
}
RootTools
  • 21
  • 1
2

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

Ashutosh Kumar
  • 459
  • 3
  • 12
2

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.

Khalil Laleh
  • 1,168
  • 10
  • 19
  • Does this handle the CF server variable, or would we also need to call `Request::setTrustedProxies(['REMOTE_ADDR'], 'HTTP_CF_CONNECTING_IP');`? – Sᴀᴍ Onᴇᴌᴀ May 31 '22 at 18:41
2

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.

matigo
  • 1,321
  • 1
  • 6
  • 16
  • Thanks for your explanations. I tried the Pseudo IPv4 but it seems not the actual IPv4 address. I can get `246.101.74.149` but my IP isn't it. I look up the IP and gets `bogon: true` and `Reserved RFC3330`. – CHOO YJ Jul 16 '21 at 05:57
  • Network connections that are IPv6 only do not have an IPv4 address assigned to them unless an intermediary device assigns a dummy IP to them. How are you determining your IPv4 address? – matigo Jul 16 '21 at 06:05
  • I used an online tool to check (this) computer IPv4: https://whatismyipaddress.com/ which is the same IP shown on my router's website. – CHOO YJ Jul 16 '21 at 06:25
  • I tried with some devices. This is my results: My computer with WiFi: dummy IP, Another computer with same WiFi: dummy IP, My phone with same WiFi: Correct IP, My phone connected to mobile data: dummy IP. – CHOO YJ Jul 16 '21 at 06:30
  • It was two days ago and I didn't get any response from you. I am going to award the best existing answer the full bounty amount. The only conclusion that I can say is, nothing can get only actual IPv4 because my computer is an example that Pseudo IPv4 can give me dummy IPv4. – CHOO YJ Jul 18 '21 at 10:29
2

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();
  • dadach 1. `REMOTE_ADDR` always contain valid data, so another conditions will never run 2. downloading a file on every request is not a good idea, may be better to cache it for 6hours – a55 Jun 26 '23 at 08:58
  • 1
    @a55 That's true, it's better to cache the downloaded IP list from Cloudflare. About `$_SERVER['REMOTE_ADDR']`, it is the safest way to check the user's real IP and prevent IP fraud but also there are other methods you can use that I tried to show them. – Majid Jalilian Jul 06 '23 at 11:01
0

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

xinqiu
  • 795
  • 6
  • 13
0

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

Igor Simic
  • 510
  • 4
  • 4
-1
function getClientIP() {
  $ipaddress = 'UNKNOWN';
  $SRC = array('HTTP_CF_CONNECTING_IP','HTTP_CLIENT_IP','HTTP_X_FORWARDED_FOR','HTTP_X_FORWARDED','HTTP_FORWARDED_FOR','HTTP_FORWARDED','REMOTE_ADDR');
  foreach($SRC as $src) {
    if (isset($_SERVER[$src])) {
        $ipaddress = $_SERVER[$src];
        continue;
    }
  }
  return $ipaddress;
}