LOADING
cursor pixel hand

isladjan blog: cloudflare web analytics proxy:
How to Beat AdBlockers

Introduction

CloudFlare Web Analytics is a great free alternative for simple web analytics on static sites that respect privacy. It doesn't require cookies and complies with EU GDPR, providing basic metrics such as unique visitors, referral source, and browser type—ideal for non-commercial websites. Although there are more advanced alternatives like Matomo and Fathom in terms of privacy and precision, CloudFlare Web Analytics remains minimalistic and lightweight, imposing minimal resource consumption and negligible performance overhead.

The problem is that, like most web analytics solutions, the CloudFlare beacon script (beacon.min.js) is often blocked by ad blockers. I'm addressing this by using a PHP backend proxy.

How CloudFlare Web Analytics Works

When the entire page initializes, the JavaScript code is executed to load Cloudflare's beacon.min.js along with your Cloudflare token, which allows Cloudflare to identify the site. You can also pass additional configuration options through the script tag using data attributes if needed. Once initialized, the beacon script begins transmitting analytics data back to Cloudflare—this may occur once or multiple times depending on your configuration. However, ad blockers tend to intercept and block both the fetching of the beacon script and the subsequent data transmission, which is why a proxy solution is required.

Solution

I rerouted the CloudFlare beacon script and its analytics data through my backend using two PHP files: cf_script.php to load and cache the script, and cf_data.php to proxy the data to CloudFlare's endpoint. If you're using a different backend, just ask ChatGPT to convert the code.

cf_loader.php
copy

<?php
/*   
    this script fetches Cloudflare's Analytics beacon.js,   

    so that AdBlockers don't block it if downloaded directly from Cloudflare
*/

// Cloudflare Analytics beacon script URL provided by Cloudflare
$remoteScriptUrl = 'https://static.cloudflareinsights.com/beacon.min.js';

// Fetch the script from the remote URL
$scriptContent = file_get_contents($remoteScriptUrl);
if ($scriptContent === false) {
    // If fetching fails, return a 500 error
    header('HTTP/1.1 500 Internal Server Error');
    exit('Error fetching the Cloudflare script.');
}

// Set header for JavaScript content
header('Content-Type: application/javascript');

echo $scriptContent;
                                
cf_data.php
copy

                                    <?php
                                    /*
                                        cf_data.php: This script acts as a proxy to forward analytics data
                                        to Cloudflare helping bypass ad blockers.
                                        First, the CF beacon.js for Web Analytics is downloaded via cf_loader.php (which serves as a redirect),
                                        and then that script starts sending data to Cloudflare through this file.

                                    */
                                    
                                    // Set CORS headers to allow requests from your domain
                                    header('Access-Control-Allow-Origin: *');
                                    header('Access-Control-Allow-Methods: GET, POST, OPTIONS');
                                    header('Access-Control-Allow-Headers: Content-Type, Accept');
                                    
                                    
                                    // Handle preflight OPTIONS request
                                    if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
                                        http_response_code(200);
                                        exit();
                                    }
                                    
                                    // Set the Cloudflare Analytics endpoint URL
                                    $cloudflareEndpoint = 'https://cloudflareinsights.com/cdn-cgi/rum';
                                    
                                    // Get the raw POST data and request headers
                                    $rawData = file_get_contents('php://input');
                                    $contentType = isset($_SERVER['CONTENT_TYPE']) ? $_SERVER['CONTENT_TYPE'] : '';
                                    $accept = isset($_SERVER['HTTP_ACCEPT']) ? $_SERVER['HTTP_ACCEPT'] : '';
                                    
                                    // Initialize cURL
                                    $ch = curl_init($cloudflareEndpoint);
                                    
                                    // Set cURL options to match exactly what beacon.min.js expects
                                    curl_setopt_array($ch, [
                                        CURLOPT_RETURNTRANSFER => true,
                                        CURLOPT_FOLLOWLOCATION => true,
                                        CURLOPT_POST => true,
                                        CURLOPT_POSTFIELDS => $rawData,
                                        CURLOPT_HTTPHEADER => [
                                            'Content-Type: ' . ($contentType ?: 'text/plain'),
                                            'Accept: ' . ($accept ?: '*/*'),
                                            'Origin: ' . (isset($_SERVER['HTTP_ORIGIN']) ? $_SERVER['HTTP_ORIGIN'] : '*'),
                                            'Referer: ' . (isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : ''),
                                            'User-Agent: ' . (isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : ''),
                                            'Content-Length: ' . strlen($rawData)
                                        ],
                                        // Additional options to handle potential SSL issues
                                        CURLOPT_SSL_VERIFYPEER => true,
                                        CURLOPT_SSL_VERIFYHOST => 2,
                                        // Timeout settings
                                        CURLOPT_TIMEOUT => 5,
                                        CURLOPT_CONNECTTIMEOUT => 3
                                    ]);
                                    
                                    // Execute the request
                                    $response = curl_exec($ch);
                                    $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
                                    
                                    // Check for errors
                                    if (curl_errno($ch)) {
                                        error_log('Cloudflare Analytics Error: ' . curl_error($ch));
                                        http_response_code(500);
                                        echo json_encode(['error' => 'Failed to forward analytics data']);
                                    } else {
                                        // Forward the HTTP status code
                                        http_response_code($httpCode);
                                    
                                        // Get and forward the content type from Cloudflare's response
                                        $responseContentType = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
                                        if ($responseContentType) {
                                            header('Content-Type: ' . $responseContentType);
                                        } else {
                                            // Default to text/plain if no content type is returned
                                            header('Content-Type: text/plain');
                                        }
                                    
                                        // Return the response exactly as received from Cloudflare
                                        echo $response;
                                    }
                                    
                                    curl_close($ch);
                               
javascript
copy

                                    function cloudflareScript() {
                                        /*
                                            this function initiates the proxy process for Cloudflare Analytics.
                                            Don't forget to update the file paths on your backend and replace
                                            the CF token with your actual token.
                                        */
                                    
                                        //correct the path to the files where they are located
                                        const urlLoader = window.location.origin + "./cf_loader.php";
                                        const urlData = window.location.origin + "./cf_data.php";
                                    
                                    
                                        // Cloudflare authentication token - replace with actual token
                                        const cfToken = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
                                    
                                        // Configure Cloudflare beacon object with required settings
                                        window.__cfBeacon = {
                                            token: cfToken,
                                            spa: false,       // Single Page Application mode disabled
                                            send: {
                                                to: urlData   // Endpoint where analytics data will be sent
                                            }
                                    
                                        };
                                    
                                        const cfScript = document.createElement("script");
                                        cfScript.defer = true;
                                        cfScript.src = urlLoader;
                                        cfScript.setAttribute("data-cf-beacon", JSON.stringify(window.__cfBeacon));
                                        document.head.appendChild(cfScript);
                                    }
                                

Note

Please be aware that Cloudflare's official documentation does not explicitly state whether proxying their analytics is allowed or forbidden. This has been interpreted as permission to proxy the data, but that policy may change in the future. It’s a good idea to periodically review the latest Cloudflare documentation to ensure ongoing compliance.

This is not the only way to proxy Cloudflare Web Analytics. You can also implement it via a Cloudflare Worker, although some ad blockers may still block this method.

Don't forget to add logic so that if the user really wants to block the script, they can do it.

Source Code on GitHub

Happy coding.