1. Get an API key
Create a free account and copy your API key from the dashboard. The Free plan includes 10,000 lookups a month. Keep the key on your server in an environment variable called VPNBLOCKER_KEY, never in code that runs in the browser.
2. Get the visitor’s real IP
Normally that’s $_SERVER['REMOTE_ADDR']. If your site is behind Cloudflare, use the address Cloudflare passes on instead:
const BEHIND_CLOUDFLARE = false; // set to true only if all traffic comes through Cloudflare
function visitor_ip(): string
{
if (BEHIND_CLOUDFLARE && !empty($_SERVER['HTTP_CF_CONNECTING_IP'])) {
return $_SERVER['HTTP_CF_CONNECTING_IP'];
}
return $_SERVER['REMOTE_ADDR'];
}
Only trust a forwarded header if every request really reaches your server through that proxy. Otherwise anyone can send a fake one and pretend to be any IP address.
3. Look up the IP
One HTTPS request with a short timeout. If the lookup fails, the function returns null so you can let the user through rather than locking everyone out.
function vpnblocker_lookup(string $ip): ?array
{
$ch = curl_init('https://api.vpnblocker.net/v2/json/' . rawurlencode($ip));
curl_setopt_array($ch, [
CURLOPT_HTTPHEADER => ['X-API-KEY: ' . getenv('VPNBLOCKER_KEY')],
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 3,
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);
return ($body !== false && $status === 200) ? json_decode($body, true) : null;
}
4. Cache the answer
Caching saves lookups and keeps pages fast. An hour is a good length, because Tor exits change every hour. This uses APCu when it’s installed:
function vpnblocker_cached(string $ip): ?array
{
$key = 'vpnblocker:' . $ip;
if (function_exists('apcu_fetch')) {
$hit = apcu_fetch($key, $found);
if ($found) {
return $hit;
}
}
$r = vpnblocker_lookup($ip);
if ($r !== null && function_exists('apcu_store')) {
apcu_store($key, $r, 3600);
}
return $r;
}
5. Decide: block, challenge or allow
This turns a lookup into one of three actions. It’s the same logic as the example in the docs: change the settings at the top to fit your site.
// Tune these to fit your site.
const BLOCK_CATEGORIES = ['vpn', 'proxy', 'tor']; // add 'hosting' to block data centers too
const BLOCK_AT = 90; // block these categories at or above this confidence
const CHALLENGE_AT = 70; // challenge anything flagged at or above this confidence
const CLEAN_TRUST_AT = 80; // clean answers below this are little-known addresses
function decide(array $r, bool $sensitive = false): string
{
$flagged = $r['host-ip'] === true || count($r['categories']) > 0;
$blockable = (bool) array_intersect($r['categories'], BLOCK_CATEGORIES);
if ($blockable && $r['confidence'] >= BLOCK_AT) return 'block';
if ($flagged && $r['confidence'] >= CHALLENGE_AT) return 'challenge';
if ($sensitive && !$flagged && $r['confidence'] < CLEAN_TRUST_AT) return 'challenge';
return 'allow';
}
6. Use it where it matters
Check on the pages abuse goes for, such as sign-up, login and checkout, rather than every page view. Pass true for $sensitive on payments.
$r = vpnblocker_cached(visitor_ip());
$action = $r === null ? 'allow' : decide($r); // allow if the lookup failed
if ($action === 'block') {
http_response_code(403);
exit('Please turn off your VPN, proxy or Tor and try again.');
}
if ($action === 'challenge') {
// show a captcha, or ask for email or phone verification
}
Laravel
In Laravel, a middleware does the same job. $request->ip() respects your trusted proxy settings. Add the key to config/services.php as 'vpnblocker' => ['key' => env('VPNBLOCKER_KEY')].
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Http;
class BlockVpn
{
public function handle(Request $request, Closure $next)
{
$ip = $request->ip();
$r = Cache::remember("vpnblocker:$ip", 3600, function () use ($ip) {
try {
$res = Http::withHeaders(['X-API-KEY' => config('services.vpnblocker.key')])
->timeout(3)
->get('https://api.vpnblocker.net/v2/json/' . rawurlencode($ip));
return $res->ok() ? $res->json() : null;
} catch (\Throwable) {
return null; // network error or timeout: let the user through
}
});
if ($r !== null && decide($r) === 'block') {
abort(403, 'Please turn off your VPN, proxy or Tor and try again.');
}
return $next($request);
}
}
Put decide() from step 5 in a helper file, then add the middleware to the routes you want to protect, for example Route::post('/register', ...)->middleware(BlockVpn::class);.