DEV Community

Ozor
Ozor

Posted on

How to Detect VPN and Proxy Users by IP Address (JavaScript)

Fraud prevention, geo-restriction enforcement, and abuse detection all share a common need: detecting when a user is hiding behind a VPN or proxy.

In this tutorial, you'll build a lightweight VPN/proxy detector in JavaScript using IP geolocation and ASN analysis — no paid threat intelligence feeds required.

How VPN Detection Works

VPN and proxy detection relies on several signals:

  1. ASN (Autonomous System) analysis — VPN providers use specific hosting companies (DigitalOcean, AWS, OVH, etc.)
  2. Data center IP detection — Residential users don't connect from data centers
  3. Geolocation anomalies — Timezone mismatches, impossible travel speeds
  4. Known VPN provider ranges — Major VPN companies own identifiable IP blocks

We'll combine these signals into a confidence score.

Step 1: Get IP Intelligence

First, grab an API key from Frostbyte (200 free credits, no card required).

async function getIPIntel(ip) {
  const API_KEY = 'your-api-key';
  const res = await fetch(
    `https://api.frostbyte.tools/api/geo/${ip}`,
    { headers: { 'x-api-key': API_KEY } }
  );
  return res.json();
}
Enter fullscreen mode Exit fullscreen mode

This returns geolocation, ISP, ASN, and organization data — everything we need.

Step 2: Build the VPN Detector

// Known hosting/VPN ASN keywords
const VPN_ASN_KEYWORDS = [
  'digitalocean', 'amazon', 'aws', 'google cloud', 'microsoft azure',
  'ovh', 'hetzner', 'linode', 'vultr', 'choopa', 'contabo',
  'cloudflare', 'akamai', 'fastly', 'leaseweb', 'psychz',
  'quadranet', 'cogent', 'zscaler', 'nordvpn', 'expressvpn',
  'surfshark', 'mullvad', 'private internet access', 'cyberghost',
  'proton', 'hide.me', 'ipvanish', 'tunnelbear', 'windscribe',
  'hostwinds', 'servermania', 'hostinger', 'ionos', 'kamatera'
];

// Known datacenter/hosting org keywords
const DATACENTER_KEYWORDS = [
  'hosting', 'cloud', 'server', 'data center', 'datacenter',
  'vps', 'dedicated', 'colocation', 'colo', 'rack',
  'infrastructure', 'network', 'telecom'
];

function analyzeIP(geoData) {
  const signals = [];
  let score = 0;

  const org = (geoData.org || '').toLowerCase();
  const isp = (geoData.isp || '').toLowerCase();
  const asn = (geoData.as || '').toLowerCase();

  // Check 1: Known VPN/hosting ASN
  for (const keyword of VPN_ASN_KEYWORDS) {
    if (org.includes(keyword) || isp.includes(keyword) || asn.includes(keyword)) {
      signals.push(`ASN match: "${keyword}"`);
      score += 40;
      break;
    }
  }

  // Check 2: Datacenter indicators in org name
  for (const keyword of DATACENTER_KEYWORDS) {
    if (org.includes(keyword) || isp.includes(keyword)) {
      signals.push(`Datacenter indicator: "${keyword}"`);
      score += 25;
      break;
    }
  }

  // Check 3: Missing or generic ISP (common with VPNs)
  if (!geoData.isp || geoData.isp === geoData.org) {
    signals.push('ISP matches org (typical of hosting providers)');
    score += 10;
  }

  // Check 4: Country mismatch with timezone (if available)
  if (geoData.timezone && geoData.country) {
    const tzCountry = geoData.timezone.split('/')[0];
    const expectedContinent = getContinent(geoData.countryCode);
    if (tzCountry === 'America' && expectedContinent === 'Europe' ||
        tzCountry === 'Europe' && expectedContinent === 'America') {
      signals.push('Timezone/country continent mismatch');
      score += 20;
    }
  }

  // Cap at 100
  score = Math.min(score, 100);

  return {
    ip: geoData.query || geoData.ip,
    country: geoData.country,
    city: geoData.city,
    isp: geoData.isp,
    org: geoData.org,
    vpnScore: score,
    isLikelyVPN: score >= 40,
    isPossibleVPN: score >= 20,
    signals,
    verdict: score >= 40 ? 'LIKELY VPN/PROXY'
           : score >= 20 ? 'SUSPICIOUS'
           : 'LIKELY RESIDENTIAL'
  };
}

function getContinent(countryCode) {
  const americas = ['US','CA','MX','BR','AR','CO','CL','PE','VE'];
  const europe = ['GB','DE','FR','IT','ES','NL','SE','NO','PL','UA','RO','CH','AT','BE','CZ'];
  if (americas.includes(countryCode)) return 'America';
  if (europe.includes(countryCode)) return 'Europe';
  return 'Other';
}
Enter fullscreen mode Exit fullscreen mode

Step 3: Full Working Script

Here's the complete detection tool you can run right now:

// vpn-detector.js — Run: node vpn-detector.js 1.2.3.4
const API_KEY = process.env.FROSTBYTE_KEY || 'your-api-key';
const API_BASE = 'https://api.frostbyte.tools/api';

const VPN_ASN_KEYWORDS = [
  'digitalocean', 'amazon', 'aws', 'google cloud', 'azure',
  'ovh', 'hetzner', 'linode', 'vultr', 'choopa', 'contabo',
  'cloudflare', 'akamai', 'leaseweb', 'nordvpn', 'expressvpn',
  'surfshark', 'mullvad', 'proton', 'ipvanish', 'cyberghost',
  'hostwinds', 'hostinger', 'kamatera', 'psychz', 'quadranet'
];

async function detectVPN(ip) {
  const res = await fetch(`${API_BASE}/geo/${ip}`, {
    headers: { 'x-api-key': API_KEY }
  });
  const geo = await res.json();

  const org = (geo.org || '').toLowerCase();
  const isp = (geo.isp || '').toLowerCase();
  let score = 0;
  const signals = [];

  // ASN check
  for (const kw of VPN_ASN_KEYWORDS) {
    if (org.includes(kw) || isp.includes(kw)) {
      signals.push(`Known VPN/hosting provider: ${kw}`);
      score += 40;
      break;
    }
  }

  // Datacenter check
  if (/hosting|cloud|server|data.?center|vps|dedicated|colo/i.test(org + isp)) {
    signals.push('Datacenter/hosting indicators in ISP name');
    score += 25;
  }

  // Residential ISPs usually have recognizable names
  const residentialPatterns = /comcast|verizon|at&t|spectrum|cox|xfinity|bt |virgin|vodafone|orange|telekom|bell |rogers|shaw /i;
  if (residentialPatterns.test(isp)) {
    signals.push('Known residential ISP detected');
    score -= 20;
  }

  score = Math.max(0, Math.min(score, 100));

  console.log(`\n  IP: ${ip}`);
  console.log(`  Location: ${geo.city}, ${geo.regionName}, ${geo.country}`);
  console.log(`  ISP: ${geo.isp}`);
  console.log(`  Org: ${geo.org}`);
  console.log(`  VPN Score: ${score}/100`);
  console.log(`  Verdict: ${score >= 40 ? 'LIKELY VPN/PROXY' : score >= 20 ? 'SUSPICIOUS' : 'RESIDENTIAL'}`);
  if (signals.length) console.log(`  Signals: ${signals.join(', ')}`);
}

// Test with multiple IPs
const ips = process.argv.slice(2);
if (ips.length === 0) {
  console.log('Usage: node vpn-detector.js <ip1> [ip2] [ip3]');
  console.log('Example: node vpn-detector.js 8.8.8.8 1.1.1.1');
  process.exit(1);
}

for (const ip of ips) await detectVPN(ip);
Enter fullscreen mode Exit fullscreen mode

Step 4: Use It in Express Middleware

Add VPN detection to your web app as middleware:

async function vpnMiddleware(req, res, next) {
  const ip = req.headers['x-forwarded-for']?.split(',')[0] || req.ip;

  // Skip private IPs
  if (/^(10\.|172\.(1[6-9]|2\d|3[01])\.|192\.168\.|127\.)/.test(ip)) {
    req.vpnCheck = { isLikelyVPN: false, score: 0 };
    return next();
  }

  try {
    const geo = await fetch(`https://api.frostbyte.tools/api/geo/${ip}`, {
      headers: { 'x-api-key': process.env.FROSTBYTE_KEY }
    }).then(r => r.json());

    const org = (geo.org || '').toLowerCase();
    const isp = (geo.isp || '').toLowerCase();
    const combined = org + ' ' + isp;

    const isHosting = /digitalocean|aws|azure|ovh|hetzner|vultr|linode|contabo|cloudflare/i.test(combined);
    const isVPN = /nordvpn|expressvpn|surfshark|mullvad|proton|cyberghost|ipvanish/i.test(combined);

    req.vpnCheck = {
      isLikelyVPN: isHosting || isVPN,
      score: isVPN ? 90 : isHosting ? 60 : 0,
      country: geo.country,
      isp: geo.isp
    };
  } catch {
    req.vpnCheck = { isLikelyVPN: false, score: 0, error: true };
  }
  next();
}

// Usage
app.use(vpnMiddleware);

app.post('/api/signup', (req, res) => {
  if (req.vpnCheck.isLikelyVPN) {
    // Flag for manual review, add CAPTCHA, or block
    console.log(`VPN signup attempt from ${req.ip} (${req.vpnCheck.isp})`);
  }
  // ...continue signup
});
Enter fullscreen mode Exit fullscreen mode

Real-World Applications

Use Case Action on VPN Detection
Fraud prevention Flag transaction for manual review
Account security Require 2FA when VPN detected
Geo-restricted content Show "content unavailable" message
Pricing pages Disable regional pricing (prevent abuse)
Rate limiting Apply stricter limits for datacenter IPs
Analytics Separate bot/VPN traffic from real users

Limitations and Ethics

This approach catches ~70-80% of VPN traffic. It won't detect:

  • Residential VPNs that route through real ISPs
  • Fresh/unknown VPN providers not in the keyword list
  • Tor exit nodes (those need a separate blocklist)

Always give users a path forward — don't silently block. Show a CAPTCHA, ask for phone verification, or explain why access is restricted.

Try It Now

Get a free API key at Frostbyte — 200 credits, no credit card. Each IP lookup uses 1 credit.

The full IP Geolocation API returns 30+ fields including ISP, ASN, organization, timezone, and coordinates — everything you need for VPN detection.


Building fraud prevention or abuse detection? The Frostbyte MCP Server gives AI agents direct access to IP intelligence, DNS lookups, and more.

Top comments (0)