All articles

How to Detect a VPN in Node.js (Step-by-Step)

A practical Node.js tutorial: call a VPN detection API from your backend, read the verdict and confidence score, and gate signups or logins on the result.

May 19, 20263 min read

This is a hands-on guide to detecting VPN traffic from a Node.js backend. We'll look up the visitor's IP against a VPN detection API, read the verdict and confidence score, and use it to gate a signup. The same pattern works for logins and checkouts.

Before you start

You'll need an API key (the free tier is enough to test) and Node 18+ so the global fetch is available. Keep the key in an environment variable — never in client code.

Test the lookup on any IP first

Step 1: get the real client IP

Behind a proxy or load balancer, req.socket.remoteAddress is your infrastructure, not the user. Read the forwarded header that your trusted proxy sets:

function getClientIp(req) {
  const xff = req.headers['x-forwarded-for'];
  if (xff) return xff.split(',')[0].trim(); // left-most = original client
  return req.socket.remoteAddress;
}

Only trust X-Forwarded-For if a proxy you control sets it; otherwise a client could spoof it.

Step 2: call the detection API

Wrap the lookup in a small helper. It returns the verdict and score, and fails open (allow) on network errors so a detection outage never locks out your users:

const API_BASE = 'https://ipscanner.io';

async function checkVpn(ip) {
  try {
    const res = await fetch(`${API_BASE}/v1/vpn/${ip}`, {
      headers: { Authorization: `Bearer ${process.env.IPSCANNER_API_KEY}` }
    });
    if (!res.ok) throw new Error(`lookup failed: ${res.status}`);
    const data = await res.json();
    return { isVpn: Boolean(data.vpn), score: data.score ?? 0 };
  } catch (err) {
    console.error('vpn check error', err);
    return { isVpn: false, score: 0 }; // fail open
  }
}

Step 3: gate a signup on the result

Now use the score to decide. Below a threshold, allow; in the middle, require extra verification; high, block:

app.post('/signup', async (req, res) => {
  const ip = getClientIp(req);
  const { isVpn, score } = await checkVpn(ip);

  if (score >= 70) {
    return res.status(403).json({ error: 'Signups from this network need verification.' });
  }
  if (isVpn || score >= 30) {
    req.session.requireEmailVerification = true; // add friction, don't block
  }

  // ...create the account...
  res.json({ ok: true });
});

Notice we challenge in the mid band rather than block — see how to block VPN users the smart way for why that matters.

Step 4: cache to save lookups

The same IP often hits you repeatedly. A short-lived cache cuts API calls without going stale:

const cache = new Map(); // ip -> { value, expires }

async function checkVpnCached(ip, ttlMs = 10 * 60 * 1000) {
  const hit = cache.get(ip);
  if (hit && hit.expires > Date.now()) return hit.value;
  const value = await checkVpn(ip);
  cache.set(ip, { value, expires: Date.now() + ttlMs });
  return value;
}

Keep the TTL short (minutes, not days) because IP risk changes — see what is IP reputation.

Going further

  • Add proxy detection to the same flow to catch proxies and Tor, not just VPNs.
  • Consolidate the signals into a single IP fraud score and gate on one number.
  • Apply the identical pattern to login and checkout, adjusting thresholds per action.

Bottom line

Detecting a VPN in Node.js is a single server-side API call: resolve the real client IP, look it up, and act on the verdict and score. Fail open on errors, cache briefly, and challenge in the mid risk band instead of blocking outright.

FAQ

Frequently asked questions

Client-side checks can be tampered with and would expose your API key. Always call the detection API from your backend, where the result is trustworthy and your credentials stay private.

Related articles