Hi! My name is Andrew Maksimchenko 👨🏻💻 I have over 10 years of versatile professional experience, taking on diverse roles like Solution Architect, Lead Software Developer, IT Jury Member, Course Creator, Mentor, and more.
In this article, I want to share practical techniques for detecting VPNs, proxies, Tor, and incognito usage — using nothing but JavaScript. These methods can help you enhance fraud prevention, enforce geo-blocking, or simply understand who’s really behind the connection. Let’s dive in!
A while ago, I traveled to China and quickly noticed how my internet experience felt… different…
Some of my favorite websites and apps were unreachable, and even basic services were routed through unfamiliar paths. So my curiosity kicked in here — and I thought — if governments can manage it on the physical layer with a few software tricks, then how can you and I, as JavaScript developers can achieve something similar in our websites or apps we build?
What if we want to:
- Deliver location-based subscription content and restrict access when VPNs, Tors, proxies, or other anonymization tools are detected?
- Integrate fraud prevention features into our apps or enforce geo-blocking for compliance and security?
- Or collect more accurate analytics by identifying masked connections?
Well, fortunately, it is possible — BUT with a few important considerations in mind 😅
Let’s get down to it together and explore the detection techniques available for us in JavaScript.
Opening Hook
VPNs (Virtual Private Networks) and other anonymization tools are often used for legitimate reasons such as privacy, security on public Wi-Fi, and bypassing geo-restrictions.
But for developers and security engineers, detecting VPN users can be crucial. In fact, many companies like Netflix, Hulu, PayPal, and banking platforms are already using VPN and proxy detection in their content delivery, fraud prevention, and compliance systems to enforce regional restrictions, protect user data, and ensure service integrity.
Here are a few common scenarios when it can also be very useful:
Detection Categories
Let’s take a look at a general anonymization infrastructure. Here’s what you typically want to detect:
- VPN — Commercial or private VPN endpoints.
- Proxy — Open HTTP/SOCKS proxies.
- Tor Exit Nodes — Part of the Tor network used for anonymity.
- Hosting Providers — Data centers, cloud services like AWS, GCP, etc.
- Incognito Mode — When users browse in private mode to avoid tracking or storage
The biggest problem on the Client-Side is that JavaScript is sandboxed here by design, so we have very limited access to networking. The browser intentionally hides low-level networking details from JavaScript to protect user privacy. But it doesn’t mean there’s nothing we can do. We can still gather valuable signals 🙌🏼
Think of the client-side as your first line of lightweight defense — not for blocking outright, but for scoring risk, logging suspicious indicators, or triggering server-side verification. So I highly recommend accounting for server-side intelligence in addition to offering a powerful hybrid detection system.
Let’s begin with pure JavaScript-based detection methods built into web standards, and then discover useful libraries and external services.
1. 🕵 Incognito (Private) Mode Detection
While there’s NO official API to detect Incognito (for good reason — privacy), there are some subtle ways to infer it based on browser behavior. But modern browsers do their best to prevent this, and most surface-level tricks have been deprecated or patched.
For instance, historically, Chrome exposed:
- Normal Mode: 5 GB — 20+ GB of storage quota (varies by disk);
- Incognito Mode: a reduced storage quota (~120 MB).
In this case, the following solution proved effective:
async function detectIncognitoMode() {
const { quota } = await navigator.storage.estimate();
if (quota < 120 * 1024 * 1024) {
console.log('Likely Incognito mode');
} else {
console.log('Likely Normal mode');
}
}
detectIncognitoMode();
Although this API is widely used in Chromium browsers themselves, it was patched over time. And now, based on a more recent analysis, Chrome allocates much more memory (~880 MB ± 50 MB), making previous detection on the client side unreliable or outright broken.
So what can we do then? Well,
- Firstly, modern browsers (like Chrome) still expose device capabilities, such as the user’s RAM, via
navigator.deviceMemory
. - And secondly, they are still able to report the available temporary storage quota via
navigator.storage.estimate()
.
Now here’s the insight:
In Incognito, browsers limit the quota significantly
So, if there’s a suspiciously low storage quota relative to the reported RAM, it strongly suggests the Incognito mode.
Given that, here’s another way we can try to detect our Incognito Mode
:
async function detectIncognitoMode() {
if (!navigator.storage?.estimate || !navigator.deviceMemory) {
console.warn('Not supported in this browser');
return null;
}
const { quota } = await navigator.storage.estimate();
const ram = navigator.deviceMemory;
// Expected quota based on RAM
const expectedQuota = ram * 1024 * 1024 * 1024;
const ratio = quota / expectedQuota;
// Typically < 0.2 in Incognito
const isPrivate = ratio < 0.2;
console.log(`Quota: ${quota}`);
console.log(`Device RAM: ${ram} GB`);
console.log(`Quota/RAM Ratio: ${ratio.toFixed(2)}`);
if (isPrivate) {
console.log('🕵️ Possibly Incognito mode (low storage relative to RAM)');
return true;
} else {
console.log('✅ Normal browsing mode');
return false;
}
}
detectIncognitoMode();
Here, we compare the browser’s reported temporary storage quota with the device’s available RAM. Then, it retrieves the total quota and RAM, calculates the expected storage capacity based on RAM, and computes the ratio between the two. If that ratio is unusually low (typically under 20%), it implies that the browser is in a restricted/private session like Incognito.
In my case, it gives me these numbers:
- Normal Mode: ratio ⇒ 34.53
- Incognito Mode: ratio ⇒ 0.1
2. 🛰 Geolocation vs IP Mismatch
The next smart privacy checking option we have on the client-side is the Geolocation API, which we can use to detect discrepancies between:
- User-granted GPS location
- IP-based location (from a 3rd-party API)
Here’s how we can do it:
- First, get the user’s actual location using the browser’s built-in
navigator.geolocation.getCurrentPosition()
function. This allows you to extract the device’s real latitude and longitude (you can learn more in the specification) - Then, query an IP geolocation service (free or paid — your choice) to retrieve the user’s approximate location based on their IP address. A few popular options are IP-API, IP Info, or IPify + Geo, AbstractAPI Geolocation, or similar.
- And ultimately, compare the two locations to calculate the distance between them to spot a mismatch. If the difference is greater than 300–500 km, it’s a strong indication that the user is connected via a VPN, proxy, or spoofed location.
Here’s an example using IP-API — a free, no-signup service that requires no API key:
async function detectVPN() {
navigator.geolocation.getCurrentPosition(async (pos) => {
const { latitude, longitude } = pos.coords;
const ipResponse = await fetch('http://ip-api.com/json/');
const ipData = await ipResponse.json();
const distance = getDistanceByLocations(
latitude,
longitude,
ipData.lat,
ipData.lon,
);
console.log(`GPS vs IP location distance: ${Math.round(distance)} km`);
if (distance > 500) {
console.log('⚠️ Possible VPN or location spoofing detected!');
} else {
console.log('✅ Geo/IP alignment looks normal.');
}
});
}
detectVPN();
Additionally, we need this standard function to calculate the distance between TWO locations:
function getDistanceByLocations(lat1, lon1, lat2, lon2) {
const R = 6371; // Radius of the earth in km
const dLat = deg2rad(lat2 - lat1);
const dLon = deg2rad(lon2 - lon1);
const a =
Math.sin(dLat / 2) * Math.sin(dLat / 2) +
Math.cos(deg2rad(lat1)) * Math.cos(deg2rad(lat2)) *
Math.sin(dLon / 2) * Math.sin(dLon / 2);
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
return R * c; // Distance in km
}
function deg2rad(deg) {
return deg * (Math.PI / 180);
}
And here are the results:
- With a normal Geo/IP alignment:
- With VPN-connected:
This solution works well in Chrome, Firefox, Safari, and Edge.
💡 Idea: Strengthen Detection on the Server Side too
To take this even further, you can enhance privacy detection on the backend (Node.js) or in an SSR app. Even if the user spoofs or manipulates browser-side signals, the server still has access to:
- The true client IP (
req.ip
,x-forwarded-for
, orsocket address
) - IP hopping between distant regions
- Sudden spikes in traffic from hosting providers
- Multiple account access from the same fingerprint
- Rate-limiting and abuse scoring
- Reverse DNS, WHOIS lookups, or integration with external services like IPInfo without CORS issues.
This allows you to build a hybrid detection model — combining real-time client signals with historical and network-level server insights.
3. 🌎 Timezone / Locale Mismatches
Another reliable technique to infer VPN or anonymized browsing is comparing the user’s browser-reported timezone with the timezone expected from their IP address. For instance, a user claims to be in Germany while their timezone is "Asia/Bangkok"
- That’s suspicious…
How does it work? 💬
Well, every browser exposes the user’s current time zone using JavaScript — Intl.DateTimeFormat()
API (ES2020+), so you can take advantage of that and use it in this way:
const browserTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
console.log('Browser Timezone:', browserTimezone);
Then you can compare this timezone against a server-side or client-accessible IP geolocation API that includes the timezone for the detected IP.
Here’s an example with the IP-API.com service, which we previously used for the same purpose:
async function detectVPN() {
const browserTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
const res = fetch('http://ip-api.com/json/')
const info = res.json()
const ipTimezone = info.timezone;
console.log('IP-based Timezone:', ipTimezone);
console.log('Browser Timezone:', browserTimezone);
if (ipTimezone !== browserTimezone) {
console.warn('⚠️ Timezone mismatch – Possible VPN or spoofed location.');
} else {
console.log('✅ Timezones match – Normal connection.');
}
}
detectVPN();
And here are the results:
- With a normal connection:
- With a VPN connection:
This solution works well in Chrome, Firefox, Safari, and Edge.
4. 🌐 WebRTC IP Leaks
Another native method (that was previously used on the client side) to detect user privacy tools (like VPNs) involved the RTCPeerConnection API. During the ICE candidate generation process in WebRTC, both local and occasionally external IP addresses could be exposed, even when a VPN was active, depending on the browser and operating system:
async function detectVPN() {
const discoveredIPs = new Set();
const pc = new RTCPeerConnection({ iceServers: [] });
pc.createDataChannel('');
pc.onicecandidate = (event) => {
if (!event.candidate) return;
const candidate = event.candidate.candidate;
const ipRegex = /([0-9]{1,3}(\.[0-9]{1,3}){3})/;
const match = candidate.match(ipRegex);
if (match) {
const ip = match[1];
if (!discoveredIPs.has(ip)) {
discoveredIPs.add(ip);
console.log('Discovered IP:', ip);
// Check against your external IP (via ipify or server echo)
// If mismatch or >1 IPs, possible VPN or proxy
}
}
};
const offer = await pc.createOffer();
pc.setLocalDescription(offer));
}
detectVPN();
BUT nowadays, modern browsers intentionally restrict access to private/local IP addresses in WebRTC. So in most cases today, you will only see:
- obfuscated (mDNS) addresses like —
c4d8a9c7-8b18-46b1-9b4d-9e6229b4.local
- instead of actual
192.168.*
or public IPs.
So it’s not our case any longer…
5. 🛰️ IP-based Privacy Detection
If you’re looking for a fast, robust, and developer-friendly way to detect whether an IP address belongs to a VPN, proxy, Tor node, or hosting provider, you can use IP-Based Privacy Detectors.
Many IP intelligence APIs help determine whether an IP address belongs to any anonymization tools:
- IPInfo.io — up to 50K requests/month free
- IPLocate.io— up to 1K requests/day free
- IPQualityScore — up to 1K requests/day free (VPN, bot, and fraud scores)
- IP2Location Proxy — Proxy types, hosting, Tor
- AbstractAPI — Easy-to-use with a free tier
One of the best free options available today is IPLocate.io. It works on both client and server sides and offers full access to privacy. flags* on their FREE plan and an enterprise-ready and feature-rich IP Intelligence API, which works well for production environments, risk scoring, geo-fencing, and fraud prevention. It includes detailed privacy fields (VPN, proxy, Tor, etc.), ASN data, carrier lookup, hosting provider detection, and more.
IPLocate.io requires user authentication to enforce fair usage limits on their free tier, so you’ll need to create an account and generate a personal API key to access the service. So for that:
- Go to https://www.iplocate.io
- Click “Sign Up for Free.”
- Sign in with your GitHub, Google, or email/password
- Confirm your email
And inside your dashboard, you’ll see your issued API token:
And now you can use this token in your requests like so:
const token = '<IPLOCATE_API_KEY>'; // Replace with your real token
const res = await fetch(`https://www.iplocate.io/api/lookup?apikey=${token}`);
const info = await res.json();
console.log('Your IP:', info.ip);
console.log('Timezone:', info.time_zone);
console.log('Location:', `lat: ${info.latitude}, lon: ${info.longitude}`);
console.log('Address:', `${info.continent}, ${info.country}, ${info.city}, ${info.postal_code}`);
if (info.privacy) {
console.log('VPN:', info.privacy.is_vpn);
console.log('Proxy:', info.privacy.is_proxy);
console.log('Tor:', info.privacy.is_tor);
console.log('Hosting Provider:', info.privacy.is_hosting);
} else {
console.log('Privacy flags not available');
}
console.log('Other info:', info);
The endpoint https://www.iplocate.io/api/lookup will automatically detect the caller’s IP and return the JSON response including a privacy object with the following fields:
const privacy = {
// ✅ True if the IP belongs to a known VPN provider
is_vpn: true,
// ✅ True if the IP is part of a known proxy service (e.g., open proxy, SOCKS, HTTP)
is_proxy: false,
// ✅ True if the IP is a Tor exit node
is_tor: false,
// ✅ True if the IP belongs to a cloud or hosting provider (AWS, Azure, etc.)
is_hosting: true,
// ✅ True if the IP has been reported for abuse, spam, or scraping activity
is_abuser: false,
// ✅ True if the IP is using any anonymization method (VPN, proxy, Tor) — a summary flag
is_anonymous: false,
// ✅ True if the IP is in a reserved (non-routable) range — indicates spoofing or misconfiguration
is_bogon: false,
// ✅ True if the IP is part of Apple’s iCloud Private Relay service (common on Safari/iOS)
is_icloud_relay: false
};
Moreover, this solution also works well on the server side 👍🏼
Finally, open your browser and see the results. For me, the result looks like this — on the left, the VPN is on, while on the right it’s off.
6. Fingerprinting & Heuristics
While services like IP-based Privacy detectors analyze the user’s network origin (e.g., IP address reputation, hosting provider, Tor node), there’s another category of detection that looks not at where the user comes from, but how their browser behaves.
This is called “browser fingerprinting” — a technique that generates a unique “signature” for each user based on various characteristics of their browser and device. It can be a screen resolution, timezone, canvas/WebGL rendering, language settings, and even audio stack behaviors — everything needed to build a uniquely identifying profile of the device.
For that purpose, there are many free and paid services managing this heavy lifting on their servers and returning detailed fingerprint data via simple API calls.
FingerPrint.com is the most widely used and battle-tested JavaScript fingerprinting library:
This library combines 100+ browser and device signals and computes a hashed visitor identifier out of them. Unlike cookies and local storage, a fingerprint stays the same in incognito/private mode and even when browser data is purged.
On a regular plan, they let you easily detect bots, spoofed browsers, and headless automation, while in the Pro version (~$99/month), they allow you to detect privacy settings like:
- privacy.isVpn
- privacy.isProxy
- privacy.isTor
- privacy.isHosting
- Incognito
Here’s a simple usage example:
import FingerprintJS from '@fingerprintjs/fingerprintjs';
// Initialize an agent at application startup.
const fpPromise = FingerprintJS.load({
apiKey: '<FP_API_TOKEN>', // Generate in the FP Dashboard
});
// Detect Privacy
fpPromise.then(fp => fp.get()).then(result => {
const { visitorId, ip, privacy } = result;
console.log('Visitor IP:', ip);
console.log('Visitor fingerprint:', visitorId);
console.log('Visitor privacy:', privacy);
});
You can play around with it in the Stackblitz sandbox.
They also come with a bunch of SDK & languages integrations (Google Tag Manager, AWS, Nginx, etc), allowing you to deploy it at different layers of your stack.
Final Thoughts
Detecting VPNs, proxies, Tor, and incognito usage is an essential layer of context that empowers developers to make smarter decisions around fraud prevention, geo-enforcement, personalization, and trust.
Combining browser behavior, network metadata, and server-side validation can give you a robust toolkit for detecting anonymized activity without compromising user experience.
BUT! Always use these tools responsibly — and remember: Visibility is Power, but Transparency builds Trust.
Respect your users and let them know about any privacy tracking! 💚
If you like this article, please give it a credit - like, short-comment, and share it with your colleagues, friends, and social network!
Should you have questions or anything related to Software Development - I’m always happy to help! Feel free to leave me a comment in the Q&A section below and join my CodeLikeAndrew news channel to keep posted on my new publications, contributions, and courses I release.
Let’s build the IT future we could be proud of together! 🚀
For those of you who enjoy technical deep dives or forward-thinking dev content, you can additionally check out my two recent articles on HackerNoon:
- Web Components: Build UI Kits for All UI Frameworks – a practical guide to building native, framework-agnostic components with zero dependencies
- Soft Skills of the IT Future: What Will Set You Apart – a thoughtful take on the human skills that will shape tomorrow’s developers