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.
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.