PHP: Get the correct IP address from a Cloudflare request.

If your PHP application is behind Cloudflare, then you will need to modify your code to retrieve the user’s correct IP address. This is because REMOTE_ADDR will be the IP address of the Cloudflare server that handled the request.

Getting the CF-Connecting-IP in PHP.

Fortunately, Cloudflare will forward us the client’s correct IP address using the Cf-Connecting-IP header. In PHP, this will be available in the $_SERVER superglobals array as HTTP_CF_CONNECTING_IP.

An example:

//Getting the CF-Connecting-IP header in PHP.
$ipAddress = $_SERVER["HTTP_CF_CONNECTING_IP"];

Unfortunately, the code above assumes that our PHP application will always be sitting behind the Cloudflare service. This won’t always be the case, as Cloudflare can be disabled for debugging purposes and you will also need to be able to run your website on a local testing environment. You also don’t want your application to break if you decide to stop using Cloudflare altogether.

The fix for this is simple:

$ipAddress = 'NA';

//Check to see if the CF-Connecting-IP header exists.
if(isset($_SERVER["HTTP_CF_CONNECTING_IP"])){
    //If it does, assume that PHP app is behind Cloudflare.
    $ipAddress = $_SERVER["HTTP_CF_CONNECTING_IP"];
} else{
    //Otherwise, use REMOTE_ADDR.
    $ipAddress = $_SERVER['REMOTE_ADDR'];
}

//Define it as a constant so that we can
//reference it throughout the app.
define('IP_ADDRESS', $ipAddress);

The PHP code above checks to see if the CF-Connecting-IP header exists. If it does, it assumes that the application is sitting behind the Cloudflare server. If it doesn’t, it uses the normal way of retrieving a visitor’s IP address.

CF-Connecting-IP spoofing.

Like most headers, the CF-Connecting-IP header can be spoofed. This means that a visitor could manually add the CF-Connecting-IP header and fool your application into thinking that the request came via Cloudflare. If they can do this, then they can give your PHP application any IP address that they want to.

To guard against this, you will need to validate the REMOTE_ADDR IP address. If the request came through Cloudflare, then the REMOTE_ADDR $_SERVER variable should contain an IP address that belongs to Cloudflare.

Fortunately, Cloudflare has a page that lists their IP ranges. This list of IP ranges is extremely useful, as it allows us to validate the IP address and make sure that the request came from their service.

An example where we validate that the request came from Cloudflare before accepting the CF-Connecting-IP header:

//IP ranges belonging to Cloudflare.
$cloudflareIPRanges = array(
    '204.93.240.0/24',
    '204.93.177.0/24',
    '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'
);

//NA by default.
$ipAddress = 'NA';

//Check to see if the CF-Connecting-IP header exists.
if(isset($_SERVER["HTTP_CF_CONNECTING_IP"])){
    
    //Assume that the request is invalid unless proven otherwise.
    $validCFRequest = false;
    
    //Make sure that the request came via Cloudflare.
    foreach($cloudflareIPRanges as $range){
        //Use the ip_in_range function from Joomla.
        if(ip_in_range($_SERVER['REMOTE_ADDR'], $range)) {
            //IP is valid. Belongs to Cloudflare.
            $validCFRequest = true;
            break;
        }
    }
    
    //If it's a valid Cloudflare request
    if($validCFRequest){
        //Use the CF-Connecting-IP header.
        $ipAddress = $_SERVER["HTTP_CF_CONNECTING_IP"];
    } else{
        //If it isn't valid, then use REMOTE_ADDR. 
        $ipAddress = $_SERVER['REMOTE_ADDR'];
    }
    
} else{
    //Otherwise, use REMOTE_ADDR.
    $ipAddress = $_SERVER['REMOTE_ADDR'];
}

//Define it as a constant so that we can
//reference it throughout the app.
define('IP_ADDRESS', $ipAddress);

Note: The ip_in_range function belongs to Joomla and can be found here.

Hopefully, you found this post useful!