Optimized GeoIP Blocking: Implementing Country-Level IP Restrictions in Apache/PHP Without Root Access


2 views

When implementing country-level IP blocking, we need to consider three critical constraints:

  • Shared hosting environment (no root access)
  • Apache server with .htaccess capability
  • PHP execution environment

For maximum efficiency, we'll implement a hybrid approach combining:

1. .htaccess for initial filtering
2. PHP fallback for dynamic verification
3. Local GeoIP database for reduced latency

First, create/modify your .htaccess file:

<IfModule mod_geoip.c>
    GeoIPEnable On
    GeoIPDBFile /path/to/GeoIP.dat
    SetEnvIf GEOIP_COUNTRY_CODE COUNTRY_TO_BLOCK BlockCountry
    Deny from env=BlockCountry
</IfModule>

For shared hosting without GeoIP module, use this alternative:

RewriteEngine on
RewriteCond %{ENV:GEOIP_COUNTRY_CODE} ^(CN|RU)$ [NC]
RewriteRule ^ - [F]

Create a geo_block.php file:

<?php
require_once 'vendor/autoload.php';
use GeoIp2\Database\Reader;

$blockedCountries = ['CN', 'RU', 'KP'];
$ip = $_SERVER['REMOTE_ADDR'];

try {
    $reader = new Reader('/path/to/GeoLite2-Country.mmdb');
    $record = $reader->country($ip);
    
    if (in_array($record->country->isoCode, $blockedCountries)) {
        header('HTTP/1.0 403 Forbidden');
        exit('Access denied from your region');
    }
} catch (Exception $e) {
    // Log error but don't block
    error_log("GeoIP error: " . $e->getMessage());
}

To reduce database lookups:

<?php
$ip = $_SERVER['REMOTE_ADDR'];
$cacheFile = sys_get_temp_dir() . '/geoip_cache.json';

// Simple caching mechanism
if (file_exists($cacheFile) && 
    time()-filemtime($cacheFile) < 86400) {
    $cache = json_decode(file_get_contents($cacheFile), true);
} else {
    $cache = [];
}

if (!isset($cache[$ip])) {
    // Perform GeoIP lookup and update cache
    // ... (previous lookup code)
    $cache[$ip] = $record->country->isoCode;
    file_put_contents($cacheFile, json_encode($cache));
}
Service Free Tier Accuracy Latency
MaxMind Yes (GeoLite) High Low
IP2Location Limited Medium Medium
Geoplugin Yes Medium High

For VPN/TOR detection:

<?php
// Check for common VPN exit nodes
$vpnRanges = [
    '185.107.80.0/24',
    '212.102.50.0/24'
    // Add more known ranges
];

foreach ($vpnRanges as $range) {
    if (ip_in_range($ip, $range)) {
        header('HTTP/1.0 403 Forbidden');
        exit('VPN/Proxy detected');
    }
}

function ip_in_range($ip, $range) {
    // Implementation of CIDR check
    // ... (omitted for brevity)
}

When implementing country-level IP blocking, we face several technical constraints, especially in shared hosting environments. The primary challenge lies in efficiently matching incoming requests against country IP ranges without causing significant performance overhead.

The most reliable approach involves using a GeoIP database. For PHP implementations, consider these options:

// Using MaxMind GeoIP2 (recommended)
require 'vendor/autoload.php';
use GeoIp2\Database\Reader;

$reader = new Reader('/path/to/GeoLite2-Country.mmdb');
$record = $reader->country($_SERVER['REMOTE_ADDR']);

if ($record->country->isoCode === 'CN') { // Block China for example
    header('HTTP/1.0 403 Forbidden');
    exit;
}

For Apache servers, you can implement country blocking via .htaccess using mod_geoip (if available) or by generating IP range rules:

# Block by country code using mod_geoip
<IfModule mod_geoip.c>
    GeoIPEnable On
    GeoIPDBFile /path/to/GeoIP.dat
    SetEnvIf GEOIP_COUNTRY_CODE CN BlockCountry
    Deny from env=BlockCountry
</IfModule>

# Alternative: Pre-generated IP blocks
Deny from 1.0.1.0/24
Deny from 1.0.2.0/23
# Add more CIDR ranges for the target country

The most efficient method depends on your traffic volume:

  • Low traffic: PHP-level checking is sufficient
  • Medium traffic: .htaccess with pre-generated CIDR blocks
  • High traffic: Server-level solutions like Cloudflare or Nginx geoip module

GeoIP databases require regular updates. Implement a cron job to fetch fresh data:

#!/bin/bash
wget -O /tmp/GeoLite2-Country.tar.gz "https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-Country&license_key=YOUR_LICENSE&suffix=tar.gz"
tar -xzf /tmp/GeoLite2-Country.tar.gz -C /tmp --strip-components=1
mv /tmp/GeoLite2-Country.mmdb /path/to/database.mmdb

For those without server access, consider these API-based solutions:

// Using ipapi.co
$response = file_get_contents("https://ipapi.co/{$_SERVER['REMOTE_ADDR']}/country/");
if (trim($response) === 'CN') {
    die('Access denied');
}