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.
<?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;
<?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);
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.