Skip to content

CloudFlare Setup

Rumen Damyanov edited this page Jul 31, 2025 · 1 revision

CloudFlare Setup

Complete guide for configuring CloudFlare to provide geolocation headers for the php-geolocation package.

Table of Contents

CloudFlare Requirements

Prerequisites

  • CloudFlare account (Free tier sufficient)
  • Domain pointed to CloudFlare nameservers
  • Website traffic flowing through CloudFlare (proxied DNS records)

Plan Compatibility

Feature Free Pro Business Enterprise
IP Geolocation
CF-IPCountry Header
Workers (Advanced) ✅ (Limited)
Page Rules ✅ (3 rules) ✅ (20 rules) ✅ (50 rules) ✅ (125 rules)

Enabling IP Geolocation

Step 1: Access CloudFlare Dashboard

  1. Log in to your CloudFlare Dashboard
  2. Select your domain
  3. Navigate to Network tab

Step 2: Enable IP Geolocation

# In CloudFlare Dashboard -> Network tab
1. Scroll down to "IP Geolocation" section
2. Toggle "IP Geolocation" to ON
3. The setting should show as "Enabled"

Important Notes:

  • This feature is available on all plans (including Free)
  • Changes may take up to 5 minutes to propagate
  • The setting applies to all traffic through CloudFlare

Visual Guide

CloudFlare Dashboard
├── Domain Selection
├── Network Tab
│   ├── HTTP/2 (Enabled)
│   ├── HTTP/3 (Enabled)
│   ├── 0-RTT Connection Resumption
│   ├── IPv6 Compatibility
│   ├── WebSockets (Enabled)
│   ├── Onion Routing
│   ├── Pseudo IPv4
│   └── IP Geolocation ← Enable this
│       └── [Toggle Switch] → ON

Header Configuration

Available CloudFlare Headers

When IP Geolocation is enabled, CloudFlare adds these headers:

CF-IPCountry: US
CF-Ray: 123abc456def789-LAX
CF-Visitor: {"scheme":"https"}
CF-Connecting-IP: 192.168.1.100

Header Details

Header Description Example Always Present
CF-IPCountry ISO 3166-1 alpha-2 country code US, GB, DE ✅ (when geolocation enabled)
CF-Ray Unique request identifier 123abc456def789-LAX
CF-Visitor Connection scheme info {"scheme":"https"}
CF-Connecting-IP Original visitor IP 192.168.1.100

Testing Headers

Create a simple PHP test file:

<?php
// cloudflare-test.php
header('Content-Type: application/json');

$headers = [
    'CF-IPCountry' => $_SERVER['HTTP_CF_IPCOUNTRY'] ?? 'Not Set',
    'CF-Ray' => $_SERVER['HTTP_CF_RAY'] ?? 'Not Set',
    'CF-Visitor' => $_SERVER['HTTP_CF_VISITOR'] ?? 'Not Set',
    'CF-Connecting-IP' => $_SERVER['HTTP_CF_CONNECTING_IP'] ?? 'Not Set',
    'Real-IP' => $_SERVER['REMOTE_ADDR'] ?? 'Not Set',
    'User-Agent' => $_SERVER['HTTP_USER_AGENT'] ?? 'Not Set'
];

echo json_encode([
    'cloudflare_status' => isset($_SERVER['HTTP_CF_RAY']) ? 'Active' : 'Not Active',
    'geolocation_enabled' => isset($_SERVER['HTTP_CF_IPCOUNTRY']) ? 'Yes' : 'No',
    'detected_country' => $_SERVER['HTTP_CF_IPCOUNTRY'] ?? null,
    'headers' => $headers,
    'timestamp' => date('Y-m-d H:i:s T')
], JSON_PRETTY_PRINT);
?>

Access this file in your browser: https://yourdomain.com/cloudflare-test.php

Expected output when properly configured:

{
    "cloudflare_status": "Active",
    "geolocation_enabled": "Yes",
    "detected_country": "US",
    "headers": {
        "CF-IPCountry": "US",
        "CF-Ray": "123abc456def789-LAX",
        "CF-Visitor": "{\"scheme\":\"https\"}",
        "CF-Connecting-IP": "203.0.113.1",
        "Real-IP": "172.16.0.1",
        "User-Agent": "Mozilla/5.0..."
    },
    "timestamp": "2025-07-30 14:30:00 UTC"
}

DNS Configuration

Proxy vs DNS Only

For geolocation to work, your DNS records must be proxied (orange cloud):

# Correct Configuration (Proxied - Orange Cloud)
A    @        203.0.113.1    [🟠] Proxied
A    www      203.0.113.1    [🟠] Proxied
CNAME api     @              [🟠] Proxied

# Incorrect Configuration (DNS Only - Gray Cloud)
A    @        203.0.113.1    [⚫] DNS Only  ← Won't work
A    www      203.0.113.1    [⚫] DNS Only  ← Won't work

Subdomain Configuration

# Main domain with geolocation
A    @        203.0.113.1    [🟠] Proxied  ← Geolocation works

# API subdomain with geolocation
A    api      203.0.113.1    [🟠] Proxied  ← Geolocation works

# CDN subdomain without geolocation (for static assets)
A    cdn      203.0.113.1    [⚫] DNS Only  ← No geolocation needed

Verification Commands

# Check if traffic flows through CloudFlare
dig yourdomain.com
# Should return CloudFlare IP addresses (not your origin server)

# Test with curl
curl -I https://yourdomain.com
# Should include CF-Ray header

# Test geolocation header
curl -H "CF-IPCountry: DE" https://yourdomain.com/cloudflare-test.php

Testing CloudFlare Integration

Comprehensive Test Script

Create cloudflare-integration-test.php:

<?php
require_once 'vendor/autoload.php';

use Rumenx\Geolocation\Geolocation;

class CloudFlareIntegrationTest
{
    private array $results = [];

    public function runTests(): array
    {
        $this->testCloudFlarePresence();
        $this->testGeolocationHeaders();
        $this->testGeolocationPackage();
        $this->testCountryDetection();
        $this->testHeaderForwarding();

        return $this->results;
    }

    private function testCloudFlarePresence(): void
    {
        $cfRay = $_SERVER['HTTP_CF_RAY'] ?? null;

        $this->results['cloudflare_presence'] = [
            'test' => 'CloudFlare Presence',
            'status' => $cfRay ? 'PASS' : 'FAIL',
            'cf_ray' => $cfRay,
            'message' => $cfRay ? 'Traffic flowing through CloudFlare' : 'Not behind CloudFlare proxy'
        ];
    }

    private function testGeolocationHeaders(): void
    {
        $cfCountry = $_SERVER['HTTP_CF_IPCOUNTRY'] ?? null;

        $this->results['geolocation_headers'] = [
            'test' => 'Geolocation Headers',
            'status' => $cfCountry ? 'PASS' : 'FAIL',
            'country_header' => $cfCountry,
            'message' => $cfCountry ? 'CF-IPCountry header present' : 'CF-IPCountry header missing - check IP Geolocation setting'
        ];
    }

    private function testGeolocationPackage(): void
    {
        try {
            $geo = new Geolocation();
            $country = $geo->getCountryCode();
            $language = $geo->getLanguage();

            $this->results['package_integration'] = [
                'test' => 'Package Integration',
                'status' => 'PASS',
                'detected_country' => $country,
                'detected_language' => $language,
                'message' => 'php-geolocation package working correctly'
            ];
        } catch (Exception $e) {
            $this->results['package_integration'] = [
                'test' => 'Package Integration',
                'status' => 'FAIL',
                'error' => $e->getMessage(),
                'message' => 'php-geolocation package error'
            ];
        }
    }

    private function testCountryDetection(): void
    {
        $cfCountry = $_SERVER['HTTP_CF_IPCOUNTRY'] ?? null;

        if ($cfCountry) {
            $geo = new Geolocation();
            $detectedCountry = $geo->getCountryCode();

            $match = $cfCountry === $detectedCountry;

            $this->results['country_detection'] = [
                'test' => 'Country Detection Accuracy',
                'status' => $match ? 'PASS' : 'WARNING',
                'cf_country' => $cfCountry,
                'detected_country' => $detectedCountry,
                'message' => $match ? 'Country detection matches CloudFlare' : 'Country detection mismatch'
            ];
        } else {
            $this->results['country_detection'] = [
                'test' => 'Country Detection Accuracy',
                'status' => 'SKIP',
                'message' => 'No CloudFlare country header to compare'
            ];
        }
    }

    private function testHeaderForwarding(): void
    {
        $expectedHeaders = [
            'HTTP_CF_IPCOUNTRY',
            'HTTP_CF_RAY',
            'HTTP_CF_CONNECTING_IP',
            'HTTP_CF_VISITOR'
        ];

        $presentHeaders = [];
        $missingHeaders = [];

        foreach ($expectedHeaders as $header) {
            if (isset($_SERVER[$header])) {
                $presentHeaders[] = $header;
            } else {
                $missingHeaders[] = $header;
            }
        }

        $allPresent = empty($missingHeaders);

        $this->results['header_forwarding'] = [
            'test' => 'Header Forwarding',
            'status' => $allPresent ? 'PASS' : 'WARNING',
            'present_headers' => $presentHeaders,
            'missing_headers' => $missingHeaders,
            'message' => $allPresent ? 'All CloudFlare headers forwarded' : 'Some headers missing'
        ];
    }

    public function generateReport(): string
    {
        $report = "=== CloudFlare Integration Test Report ===\n\n";
        $report .= "Generated: " . date('Y-m-d H:i:s T') . "\n\n";

        foreach ($this->results as $test) {
            $status = $test['status'];
            $icon = $status === 'PASS' ? '' : ($status === 'WARNING' ? '⚠️' : '');

            $report .= "$icon {$test['test']}: {$status}\n";
            $report .= "   Message: {$test['message']}\n";

            if (isset($test['cf_country'])) {
                $report .= "   CF Country: {$test['cf_country']}\n";
            }
            if (isset($test['detected_country'])) {
                $report .= "   Detected: {$test['detected_country']}\n";
            }
            if (isset($test['error'])) {
                $report .= "   Error: {$test['error']}\n";
            }

            $report .= "\n";
        }

        return $report;
    }
}

// Run the test
$test = new CloudFlareIntegrationTest();
$results = $test->runTests();

// Display results
header('Content-Type: text/plain');
echo $test->generateReport();

// Also output as JSON for API consumption
if (isset($_GET['format']) && $_GET['format'] === 'json') {
    header('Content-Type: application/json');
    echo json_encode($results, JSON_PRETTY_PRINT);
}
?>

Quick Test Commands

# Test from command line
curl -s "https://yourdomain.com/cloudflare-integration-test.php" | head -20

# Test with specific country header (CloudFlare simulation)
curl -H "CF-IPCountry: GB" "https://yourdomain.com/cloudflare-integration-test.php"

# Get JSON format
curl -s "https://yourdomain.com/cloudflare-integration-test.php?format=json" | jq .

# Test multiple endpoints
for url in yourdomain.com www.yourdomain.com api.yourdomain.com; do
    echo "Testing $url..."
    curl -sI "https://$url" | grep -E "(CF-Ray|CF-IPCountry)"
    echo ""
done

CloudFlare Workers (Advanced)

Enhanced Geolocation Worker

CloudFlare Workers can enhance geolocation data:

// Enhanced Geolocation Worker
addEventListener('fetch', event => {
    event.respondWith(handleRequest(event.request))
})

async function handleRequest(request) {
    // Get original response
    const response = await fetch(request)

    // Get geolocation data
    const country = request.cf.country
    const region = request.cf.region
    const city = request.cf.city
    const timezone = request.cf.timezone
    const continent = request.cf.continent
    const latitude = request.cf.latitude
    const longitude = request.cf.longitude

    // Create enhanced headers
    const newHeaders = new Headers(response.headers)

    // Add enhanced geolocation headers
    newHeaders.set('CF-IPCountry', country || 'XX')
    newHeaders.set('X-Geo-Country', country || 'XX')
    newHeaders.set('X-Geo-Region', region || '')
    newHeaders.set('X-Geo-City', city || '')
    newHeaders.set('X-Geo-Timezone', timezone || '')
    newHeaders.set('X-Geo-Continent', continent || '')
    newHeaders.set('X-Geo-Coordinates', `${latitude || 0},${longitude || 0}`)

    // Add currency based on country
    const currency = getCurrencyForCountry(country)
    newHeaders.set('X-Geo-Currency', currency)

    // Add language suggestions
    const languages = getLanguagesForCountry(country)
    newHeaders.set('X-Geo-Languages', languages.join(','))

    // Return response with enhanced headers
    return new Response(response.body, {
        status: response.status,
        statusText: response.statusText,
        headers: newHeaders
    })
}

function getCurrencyForCountry(country) {
    const currencies = {
        'US': 'USD', 'CA': 'CAD', 'GB': 'GBP', 'DE': 'EUR', 'FR': 'EUR',
        'ES': 'EUR', 'IT': 'EUR', 'NL': 'EUR', 'JP': 'JPY', 'AU': 'AUD',
        'BR': 'BRL', 'IN': 'INR', 'CN': 'CNY', 'RU': 'RUB', 'KR': 'KRW'
    }

    // EU countries default to EUR
    const euCountries = ['AT', 'BE', 'BG', 'HR', 'CY', 'CZ', 'DK', 'EE', 'FI', 'FR', 'DE', 'GR', 'HU', 'IE', 'IT', 'LV', 'LT', 'LU', 'MT', 'NL', 'PL', 'PT', 'RO', 'SK', 'SI', 'ES', 'SE']

    if (euCountries.includes(country)) {
        return 'EUR'
    }

    return currencies[country] || 'USD'
}

function getLanguagesForCountry(country) {
    const languages = {
        'US': ['en'], 'CA': ['en', 'fr'], 'GB': ['en'], 'AU': ['en'],
        'DE': ['de'], 'AT': ['de'], 'CH': ['de', 'fr', 'it'],
        'FR': ['fr'], 'BE': ['fr', 'nl'], 'ES': ['es'], 'IT': ['it'],
        'NL': ['nl'], 'PT': ['pt'], 'BR': ['pt'], 'RU': ['ru'],
        'JP': ['ja'], 'CN': ['zh'], 'KR': ['ko'], 'IN': ['hi', 'en']
    }

    return languages[country] || ['en']
}

Worker Route Configuration

# CloudFlare Dashboard -> Workers -> Routes
Route Pattern: *yourdomain.com/*
Worker: enhanced-geolocation-worker

Using Enhanced Headers in PHP

<?php
// Enhanced geolocation data from CloudFlare Workers
class EnhancedCloudFlareGeolocation extends Geolocation
{
    public function getRegion(): string
    {
        return $_SERVER['HTTP_X_GEO_REGION'] ?? parent::getRegion();
    }

    public function getCity(): string
    {
        return $_SERVER['HTTP_X_GEO_CITY'] ?? parent::getCity();
    }

    public function getTimezone(): string
    {
        return $_SERVER['HTTP_X_GEO_TIMEZONE'] ?? $this->getDefaultTimezone();
    }

    public function getContinent(): string
    {
        return $_SERVER['HTTP_X_GEO_CONTINENT'] ?? '';
    }

    public function getCoordinates(): array
    {
        $coords = $_SERVER['HTTP_X_GEO_COORDINATES'] ?? '';
        if ($coords) {
            [$lat, $lng] = explode(',', $coords);
            return ['latitude' => (float)$lat, 'longitude' => (float)$lng];
        }
        return ['latitude' => 0, 'longitude' => 0];
    }

    public function getCurrency(): string
    {
        return $_SERVER['HTTP_X_GEO_CURRENCY'] ?? 'USD';
    }

    public function getSuggestedLanguages(): array
    {
        $languages = $_SERVER['HTTP_X_GEO_LANGUAGES'] ?? '';
        return $languages ? explode(',', $languages) : ['en'];
    }

    private function getDefaultTimezone(): string
    {
        $timezones = [
            'US' => 'America/New_York', 'CA' => 'America/Toronto',
            'GB' => 'Europe/London', 'DE' => 'Europe/Berlin',
            'FR' => 'Europe/Paris', 'JP' => 'Asia/Tokyo'
        ];

        return $timezones[$this->getCountryCode()] ?? 'UTC';
    }
}

// Usage
$geo = new EnhancedCloudFlareGeolocation();
echo "Timezone: " . $geo->getTimezone() . "\n";
echo "Currency: " . $geo->getCurrency() . "\n";
echo "Coordinates: " . json_encode($geo->getCoordinates()) . "\n";

Page Rules for Geolocation

Country-Based Page Rules

# CloudFlare Dashboard -> Page Rules

# Rule 1: Redirect non-US traffic to international site
URL Pattern: *yourdomain.com/*
Settings:
  - If Country ≠ US: Forwarding URL (Status Code: 302)
    Destination: https://international.yourdomain.com/$1

# Rule 2: Cache settings by region
URL Pattern: *yourdomain.com/api/*
Settings:
  - If Country = US: Cache Level = Standard
  - If Country ≠ US: Cache Level = Aggressive

# Rule 3: Security settings for specific countries
URL Pattern: *yourdomain.com/admin/*
Settings:
  - If Country ∉ [US, CA, GB]: Security Level = I'm Under Attack

Advanced Page Rules

# Geographic Load Balancing (Business+ plans)
Rule: Geographic Distribution
URL: *yourdomain.com/*
Settings:
  - North America → us-east.yourdomain.com
  - Europe → eu-west.yourdomain.com
  - Asia Pacific → ap-southeast.yourdomain.com

# Language-based redirects
Rule: Auto Language Redirect
URL: yourdomain.com/
Settings:
  - If Accept-Language contains 'fr' AND Country ∈ [FR, CA, BE]:
    Redirect to /fr/
  - If Accept-Language contains 'de' AND Country ∈ [DE, AT, CH]:
    Redirect to /de/

SSL and Security Settings

SSL Configuration

# CloudFlare Dashboard -> SSL/TLS
SSL/TLS Encryption Mode: Full (strict)
Always Use HTTPS: On
HTTP Strict Transport Security (HSTS): Enabled
  - Max Age Header: 6 months
  - Include Subdomains: On
  - Preload: On

Security Settings for Geolocation

<?php
// Secure geolocation handling
class SecureCloudFlareGeolocation extends Geolocation
{
    private array $trustedProxies = [
        // CloudFlare IP ranges (simplified - use full list in production)
        '103.21.244.0/22', '103.22.200.0/22', '103.31.4.0/22',
        '104.16.0.0/12', '108.162.192.0/18', '131.0.72.0/22',
        '141.101.64.0/18', '162.158.0.0/15', '172.64.0.0/13',
        '173.245.48.0/20', '188.114.96.0/20', '190.93.240.0/20',
        '197.234.240.0/22', '198.41.128.0/17'
    ];

    public function __construct(array $server = null, array $countryToLanguage = [], string $cookieName = 'language')
    {
        // Validate that request comes from CloudFlare
        if (!$this->isFromCloudFlare()) {
            throw new SecurityException('Request not from trusted CloudFlare proxy');
        }

        // Validate geolocation headers
        $server = $this->validateHeaders($server ?: $_SERVER);

        parent::__construct($server, $countryToLanguage, $cookieName);
    }

    private function isFromCloudFlare(): bool
    {
        $clientIP = $_SERVER['REMOTE_ADDR'] ?? '';

        // Check if client IP is in CloudFlare ranges
        foreach ($this->trustedProxies as $range) {
            if ($this->ipInRange($clientIP, $range)) {
                return true;
            }
        }

        // Also check for CF-Ray header as backup
        return isset($_SERVER['HTTP_CF_RAY']);
    }

    private function validateHeaders(array $server): array
    {
        $clean = [];

        // Validate CF-IPCountry
        if (isset($server['HTTP_CF_IPCOUNTRY'])) {
            $country = strtoupper(trim($server['HTTP_CF_IPCOUNTRY']));
            if (preg_match('/^[A-Z]{2}$/', $country)) {
                $clean['HTTP_CF_IPCOUNTRY'] = $country;
            }
        }

        // Validate other headers
        $safeHeaders = [
            'HTTP_CF_RAY', 'HTTP_CF_VISITOR', 'HTTP_CF_CONNECTING_IP',
            'HTTP_ACCEPT_LANGUAGE', 'HTTP_USER_AGENT'
        ];

        foreach ($safeHeaders as $header) {
            if (isset($server[$header])) {
                $clean[$header] = $this->sanitizeHeader($server[$header]);
            }
        }

        return $clean;
    }

    private function sanitizeHeader(string $value): string
    {
        // Remove potentially dangerous characters
        $clean = preg_replace('/[^\w\-.,;:\/\s]/', '', $value);
        return substr(trim($clean), 0, 500);
    }

    private function ipInRange(string $ip, string $range): bool
    {
        [$subnet, $mask] = explode('/', $range);
        return (ip2long($ip) & ~((1 << (32 - $mask)) - 1)) === ip2long($subnet);
    }
}

class SecurityException extends Exception {}

// Usage
try {
    $geo = new SecureCloudFlareGeolocation();
    echo "Secure geolocation: " . $geo->getCountryCode();
} catch (SecurityException $e) {
    // Handle untrusted request
    error_log("Untrusted geolocation request: " . $e->getMessage());
    $geo = Geolocation::simulate('US'); // Fallback
}

Troubleshooting CloudFlare Issues

Common Issues and Solutions

Issue 1: CF-IPCountry Header Missing

Symptoms:

  • $_SERVER['HTTP_CF_IPCOUNTRY'] is not set
  • php-geolocation always returns default country

Diagnosis:

# Check if behind CloudFlare
curl -I https://yourdomain.com | grep CF-Ray

# Check geolocation setting
# Visit CloudFlare Dashboard -> Network -> IP Geolocation

Solutions:

  1. Ensure IP Geolocation is enabled in CloudFlare Dashboard
  2. Verify DNS records are proxied (orange cloud)
  3. Wait 5-10 minutes for changes to propagate
  4. Clear CloudFlare cache

Issue 2: Incorrect Country Detection

Symptoms:

  • Wrong country detected
  • Country changes unexpectedly

Diagnosis:

<?php
// Debug country detection
echo "CF Country: " . ($_SERVER['HTTP_CF_IPCOUNTRY'] ?? 'Missing') . "\n";
echo "CF Ray: " . ($_SERVER['HTTP_CF_RAY'] ?? 'Missing') . "\n";
echo "Real IP: " . ($_SERVER['REMOTE_ADDR'] ?? 'Missing') . "\n";
echo "Connecting IP: " . ($_SERVER['HTTP_CF_CONNECTING_IP'] ?? 'Missing') . "\n";

// Test from different locations
$testIPs = ['8.8.8.8', '1.1.1.1', '208.67.222.222'];
foreach ($testIPs as $ip) {
    echo "IP $ip location: " . file_get_contents("http://ip-api.com/line/$ip?fields=country") . "\n";
}
?>

Solutions:

  1. Check if using VPN/proxy
  2. Verify CloudFlare has correct IP geolocation data
  3. Use CloudFlare's IP lookup tool
  4. Consider using multiple geolocation sources

Issue 3: Headers Not Reaching Application

Symptoms:

  • CloudFlare working but headers missing in PHP
  • Headers visible in browser dev tools but not in $_SERVER

Diagnosis:

<?php
// Check all HTTP headers
foreach ($_SERVER as $key => $value) {
    if (strpos($key, 'HTTP_') === 0) {
        echo "$key: $value\n";
    }
}

// Check if headers are being stripped
phpinfo(INFO_VARIABLES);
?>

Solutions:

  1. Check web server configuration (Apache/Nginx)
  2. Verify no proxy stripping headers
  3. Check if behind additional load balancer
  4. Ensure CloudFlare proxy is closest to visitor

Issue 4: Local Development Issues

Symptoms:

  • No CloudFlare headers in local environment
  • Cannot test geolocation locally

Solutions:

<?php
// Local development simulation
class LocalCloudFlareSimulator
{
    public static function simulate(string $country = 'US'): void
    {
        if (!isset($_SERVER['HTTP_CF_IPCOUNTRY'])) {
            $_SERVER['HTTP_CF_IPCOUNTRY'] = $country;
            $_SERVER['HTTP_CF_RAY'] = 'local-' . time() . '-DEV';
            $_SERVER['HTTP_CF_VISITOR'] = '{"scheme":"https"}';
            $_SERVER['HTTP_CF_CONNECTING_IP'] = '127.0.0.1';
        }
    }
}

// Use in local development
if (in_array($_SERVER['HTTP_HOST'] ?? '', ['localhost', '127.0.0.1']) ||
    strpos($_SERVER['HTTP_HOST'] ?? '', '.local') !== false) {
    LocalCloudFlareSimulator::simulate('DE'); // Test with Germany
}

$geo = new Geolocation();
echo "Country: " . $geo->getCountryCode(); // Will show 'DE' locally
?>

Debugging Tools

CloudFlare Debug Script

<?php
// cloudflare-debug.php
class CloudFlareDebugger
{
    public function generateDebugReport(): string
    {
        $report = "=== CloudFlare Debug Report ===\n";
        $report .= "Generated: " . date('Y-m-d H:i:s T') . "\n\n";

        // Check CloudFlare presence
        $report .= "CloudFlare Status:\n";
        $report .= "- Behind CloudFlare: " . (isset($_SERVER['HTTP_CF_RAY']) ? 'Yes' : 'No') . "\n";
        $report .= "- CF-Ray: " . ($_SERVER['HTTP_CF_RAY'] ?? 'Missing') . "\n";
        $report .= "- Geolocation Enabled: " . (isset($_SERVER['HTTP_CF_IPCOUNTRY']) ? 'Yes' : 'No') . "\n\n";

        // Headers analysis
        $report .= "CloudFlare Headers:\n";
        $cfHeaders = ['HTTP_CF_IPCOUNTRY', 'HTTP_CF_RAY', 'HTTP_CF_VISITOR', 'HTTP_CF_CONNECTING_IP'];
        foreach ($cfHeaders as $header) {
            $value = $_SERVER[$header] ?? 'Missing';
            $report .= "- $header: $value\n";
        }
        $report .= "\n";

        // Network information
        $report .= "Network Information:\n";
        $report .= "- Server IP: " . ($_SERVER['SERVER_ADDR'] ?? 'Unknown') . "\n";
        $report .= "- Remote IP: " . ($_SERVER['REMOTE_ADDR'] ?? 'Unknown') . "\n";
        $report .= "- Forwarded For: " . ($_SERVER['HTTP_X_FORWARDED_FOR'] ?? 'None') . "\n";
        $report .= "- Real IP: " . ($_SERVER['HTTP_X_REAL_IP'] ?? 'None') . "\n\n";

        // Test geolocation package
        $report .= "Geolocation Package Test:\n";
        try {
            $geo = new \Rumenx\Geolocation\Geolocation();
            $report .= "- Country: " . $geo->getCountryCode() . "\n";
            $report .= "- Language: " . $geo->getLanguage() . "\n";
            $report .= "- City: " . $geo->getCity() . "\n";
            $report .= "- Region: " . $geo->getRegion() . "\n";
            $report .= "- Status: Working\n";
        } catch (Exception $e) {
            $report .= "- Error: " . $e->getMessage() . "\n";
            $report .= "- Status: Failed\n";
        }

        return $report;
    }

    public function checkDNSConfiguration(): array
    {
        $domain = $_SERVER['HTTP_HOST'] ?? 'localhost';
        $results = [];

        // Check DNS records
        $records = dns_get_record($domain, DNS_A);
        $results['dns_records'] = $records;

        // Check if IPs are CloudFlare
        $cfRanges = ['104.16.0.0/12', '162.158.0.0/15', '172.64.0.0/13', '173.245.48.0/20'];
        $results['cloudflare_ips'] = [];

        foreach ($records as $record) {
            if (isset($record['ip'])) {
                $isCloudFlare = false;
                foreach ($cfRanges as $range) {
                    if ($this->ipInRange($record['ip'], $range)) {
                        $isCloudFlare = true;
                        break;
                    }
                }
                $results['cloudflare_ips'][] = [
                    'ip' => $record['ip'],
                    'is_cloudflare' => $isCloudFlare
                ];
            }
        }

        return $results;
    }

    private function ipInRange(string $ip, string $range): bool
    {
        [$subnet, $mask] = explode('/', $range);
        return (ip2long($ip) & ~((1 << (32 - $mask)) - 1)) === ip2long($subnet);
    }
}

// Run debugger
$debugger = new CloudFlareDebugger();

// Output report
header('Content-Type: text/plain');
echo $debugger->generateDebugReport();

echo "\n=== DNS Configuration ===\n";
$dnsConfig = $debugger->checkDNSConfiguration();
print_r($dnsConfig);
?>

Performance Monitoring

<?php
// Monitor CloudFlare geolocation performance
class CloudFlareMonitor
{
    private string $logFile = '/var/log/cloudflare-geo.log';

    public function logRequest(): void
    {
        $data = [
            'timestamp' => microtime(true),
            'country' => $_SERVER['HTTP_CF_IPCOUNTRY'] ?? null,
            'cf_ray' => $_SERVER['HTTP_CF_RAY'] ?? null,
            'ip' => $_SERVER['REMOTE_ADDR'] ?? null,
            'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? null,
            'url' => $_SERVER['REQUEST_URI'] ?? null
        ];

        file_put_contents($this->logFile, json_encode($data) . "\n", FILE_APPEND | LOCK_EX);
    }

    public function getStats(int $hours = 24): array
    {
        $lines = file($this->logFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
        $since = time() - ($hours * 3600);
        $stats = ['total' => 0, 'countries' => [], 'missing_geo' => 0];

        foreach ($lines as $line) {
            $data = json_decode($line, true);
            if ($data && $data['timestamp'] > $since) {
                $stats['total']++;

                if ($data['country']) {
                    $stats['countries'][$data['country']] = ($stats['countries'][$data['country']] ?? 0) + 1;
                } else {
                    $stats['missing_geo']++;
                }
            }
        }

        return $stats;
    }
}

// Usage
$monitor = new CloudFlareMonitor();
$monitor->logRequest();

// View stats
$stats = $monitor->getStats(24);
echo "Last 24 hours: " . $stats['total'] . " requests\n";
echo "Missing geo: " . $stats['missing_geo'] . " requests\n";
echo "Top countries: " . json_encode(array_slice($stats['countries'], 0, 5, true)) . "\n";
?>

Next Steps


Previous: CodeIgniter Integration | Next: Production Best Practices

Clone this wiki locally