CloudFlare and logging visitor IP addresses via in PHP
Asked Answered
P

13

114

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?

Petulant answered 20/2, 2013 at 16:56 Comment(0)
J
319

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.

Joiejoin answered 20/2, 2013 at 17:3 Comment(7)
If Security matters one should check if the request comes from a Cloudflare IP Range ( cloudflare.com/ips ). Like, for example in this Joomla! Plugin : github.com/piccaso/joomla-cf/blob/master/cf/cf.phpZwinglian
Cloudflare also have this code listed on their site. support.cloudflare.com/hc/en-us/articles/…Sera
http_cf_connecting_ip sometimes gives things like: 2a01:6500:a043:33a6:b36a:7259:4de5:2500 any idea why?Vesicant
@Vesicant thats because its returning your ipv6.Inhere
The $_SERVER["HTTP_X_FORWARDED_FOR"] also availabe, but it is not reliable alwaysFlong
HTTP_CF_CONNECTING_IP sometimes gives IPv6, is there any solution to get only IPv4? I saw something called Pseudo IPv4 in Cloudflare.Scutter
It is worth adding that you may need to enable IP Geolocation in the Cloudflare Dashboard through the Network app. Source : support.cloudflare.com/hc/en-us/articles/…Debit
R
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!

Rhatany answered 11/7, 2016 at 1:37 Comment(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: 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: cloudflare.com/ips-v4Nevarez
U
17

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'];
Unesco answered 16/4, 2016 at 17:3 Comment(4)
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.Nevarez
@Nevarez Not if your server is properly configured and behind Cloudflare.Unesco
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 answerNevarez
@Nevarez 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.Unesco
D
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

Douty answered 6/5, 2015 at 13:49 Comment(3)
2019 - They no longer support mod_cloudflare, yet it can be used. There is something new called mod_remoteip. Reference: support.cloudflare.com/hc/en-us/articles/…Monti
@Monti probably best to create a new answer, as my answer for mod_cloudflare was from 4 years agoDouty
@sjagr in regards to your edits, they are incorrect - CloudFlare is actually Cloudflare, as I originally postedDouty
M
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

Monro answered 17/4, 2018 at 13:45 Comment(0)
D
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'];
}
Denotative answered 17/4, 2017 at 13:36 Comment(0)
A
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

Avlona answered 12/8, 2017 at 23:22 Comment(0)
E
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.

Equity answered 20/11, 2020 at 12:38 Comment(1)
Does this handle the CF server variable, or would we also need to call Request::setTrustedProxies(['REMOTE_ADDR'], 'HTTP_CF_CONNECTING_IP');?Incapacity
S
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.

Scullion answered 16/7, 2021 at 5:33 Comment(5)
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.Scutter
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?Scullion
I used an online tool to check (this) computer IPv4: whatismyipaddress.com which is the same IP shown on my router's website.Scutter
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.Scutter
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.Scutter
C
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();
Cither answered 10/2, 2022 at 6:14 Comment(2)
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 6hoursWarner
@Warner 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.Cither
F
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

Farman answered 31/3, 2017 at 3:44 Comment(0)
S
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

Swinford answered 31/3, 2022 at 7:7 Comment(0)
O
-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;
}
Orose answered 28/6, 2023 at 12:11 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.