How to Detect Proxies, VPNs, and Tor Nodes - Technical Guide
Learn how to detect VPNs, proxies, and Tor exit nodes using IP intelligence APIs. Protect your application from fraud and abusive traffic.
Detecting proxies, VPNs, and Tor nodes is essential for fraud prevention, preventing fake account creation, and blocking abusive traffic. This guide covers detection methods and implementation strategies.
Why Detect Proxies and VPNs?
Fraud Prevention
- Block suspicious transactions from masked IPs
- Identify multi-account users bypassing restrictions
- Prevent stolen credit card usage
Security
- Block traffic from known bad IPs
- Prevent brute-force attacks from proxy networks
- Detect automated bot traffic
Compliance
- Enforce geographic content restrictions
- Meet regulatory requirements for user verification
- Prevent access from sanctioned regions
Detection Methods
1. IP Database Matching
The most reliable method is checking against known proxy/VPN databases:
async function checkProxyStatus(ip) { const response = await fetch(`https://api.ipbot.com/${ip}`); const data = await response.json();
return { isProxy: data.is_proxy || false, isVPN: data.is_vpn || false, isTor: data.is_tor || false, isHosting: data.is_hosting || false, riskScore: data.risk_score || 0, };}2. Behavior Analysis
Combine IP data with behavioral signals:
function analyzeSuspiciousBehavior(ipData, userBehavior) { const riskFactors = [];
if (ipData.is_vpn || ipData.is_proxy) { riskFactors.push("proxy_or_vpn"); }
if (ipData.is_tor) { riskFactors.push("tor_exit_node"); }
if (ipData.is_hosting) { riskFactors.push("hosting_provider"); }
if (userBehavior.loginAttempts > 5) { riskFactors.push("multiple_failed_logins"); }
if (userBehavior.speed < 100) { // Abnormally fast form filling riskFactors.push("automated_behavior"); }
return { riskLevel: riskFactors.length, factors: riskFactors, shouldBlock: riskFactors.length >= 2, };}3. TCP/IP Fingerprinting
Advanced detection using connection characteristics:
// Requires server-side implementationfunction analyzeTCPFingerprint(connection) { return { ttl: connection.ttl, // Time To Live values vary by VPN tcpOptions: connection.tcpOptions, windowSize: connection.windowSize, // Compare against known VPN fingerprints };}Understanding Different Proxy Types
| Type | Description | Detectability |
|---|---|---|
| Transparent Proxy | Forwards client IP in headers | Easy (check headers) |
| Anonymous Proxy | Hides client IP | Medium (database lookup) |
| Elite Proxy | Hides proxy existence | Hard (behavior analysis) |
| VPN | Encrypted tunnel | Medium (database + timing) |
| Tor | Multi-hop routing | Easy (known exit nodes) |
| Data Center IP | Hosting/Cloud provider | Easy (ASN analysis) |
Implementation Examples
Basic VPN Detection
async function shouldAllowAccess(ip) { const ipInfo = await fetch(`https://api.ipbot.com/${ip}`).then((r) => r.json(), );
// Block known threats if (ipInfo.is_tor || ipInfo.risk_score > 80) { return false; }
// Optionally allow VPN with additional verification if (ipInfo.is_vpn || ipInfo.is_proxy) { return requireAdditionalVerification(); }
return true;}Risk-Based Authentication
async function getAuthRequirement(ip) { const ipInfo = await fetch(`https://api.ipbot.com/${ip}`).then((r) => r.json(), );
// Calculate risk score let risk = 0;
if (ipInfo.is_vpn) risk += 30; if (ipInfo.is_proxy) risk += 40; if (ipInfo.is_tor) risk += 50; if (ipInfo.is_hosting) risk += 20; if (ipInfo.risk_score > 50) risk += ipInfo.risk_score / 2;
// Return auth level based on risk if (risk >= 80) return "BLOCK"; if (risk >= 50) return "TWO_FACTOR"; if (risk >= 20) return "EMAIL_VERIFICATION"; return "STANDARD";}Python Implementation
import requestsfrom functools import lru_cache
@lru_cache(maxsize=1000)def get_ip_intel(ip: str, ttl: int = 3600) -> dict: """Get IP intelligence with caching""" response = requests.get(f"https://api.ipbot.com/{ip}") return response.json()
def should_block_user(ip: str, user_data: dict) -> bool: """Determine if user should be blocked""" intel = get_ip_intel(ip)
# Hard blocks if intel.get("is_tor"): return True
if intel.get("risk_score", 0) > 80: return True
# Soft blocks with conditions if intel.get("is_vpn") or intel.get("is_proxy"): # New accounts via VPN get blocked if user_data.get("account_age_days", 0) < 7: return True
return FalseDetecting Tor Exit Nodes
Tor exit nodes are publicly listed and easily detectable:
async function isTorExitNode(ip) { const response = await fetch(`https://api.ipbot.com/${ip}`); const data = await response.json();
// Check multiple indicators return { isTor: data.is_tor, torExit: data.tor_exit, onionRouting: data.onion_routing, reason: data.risk_reasons?.includes("tor_exit_node"), };}Handling False Positives
Not all VPN usage is malicious. Consider these scenarios:
Legitimate VPN Use Cases
- Remote workers accessing corporate resources
- Users in restrictive regions
- Privacy-conscious users
- Travelers using public WiFi
Progressive Response Strategy
function getResponseLevel(ipData, userHistory) { // Trusted users get full access regardless of VPN if (userHistory.isEstablished && userHistory.noFraud) { return "FULL_ACCESS"; }
// New users with VPN if (ipData.is_vpn || ipData.is_proxy) { if (userHistory.hasPaymentMethod) { return "FULL_ACCESS"; // Verified payment } return "LIMITED_ACCESS"; // Require verification }
// Tor = always suspicious if (ipData.is_tor) { return "BLOCK_OR_CAPTCHA"; }
return "FULL_ACCESS";}API Response Structure
IPBot returns proxy detection data:
{ "ip": "8.8.8.8", "is_proxy": false, "is_vpn": false, "is_tor": false, "is_hosting": true, "proxy_type": null, "risk_score": 0, "risk_reasons": []}Advanced Detection Strategies
1. Velocity Checking
Detect rapid requests from different IPs:
const ipTracker = new Map();
function checkVelocity(userId, ip) { const key = userId; const now = Date.now();
if (!ipTracker.has(key)) { ipTracker.set(key, [{ ip, time: now }]); return false; }
const history = ipTracker.get(key); const recentIPs = history.filter((h) => now - h.time < 60000); // 1 minute
if (recentIPs.length > 5) { const uniqueIPs = new Set(recentIPs.map((h) => h.ip)); if (uniqueIPs.size > 3) { return true; // Suspicious: many different IPs } }
history.push({ ip, time: now }); ipTracker.set(key, recentIPs); return false;}2. ASN Analysis
Check if IP belongs to known VPN providers:
const knownVPNProviders = [ "M247 Ltd", "DataCamp Limited", "Cloudflare, Inc.", "DigitalOcean, LLC",];
async function checkASN(ip) { const data = await fetch(`https://api.ipbot.com/${ip}`).then((r) => r.json());
const isKnownVPN = knownVPNProviders.some((provider) => data.organization?.includes(provider), );
return { asn: data.asn, organization: data.organization, isKnownVPN, };}3. Cross-Referencing Multiple Sources
async function comprehensiveCheck(ip) { const [ipbot, secondary] = await Promise.all([ fetch(`https://api.ipbot.com/${ip}`).then((r) => r.json()), fetch(`https://secondary-api.com/${ip}`).then((r) => r.json()), ]);
return { consensus: { isProxy: ipbot.is_proxy || secondary.is_proxy, isVPN: ipbot.is_vpn || secondary.is_vpn, confidence: ipbot.is_proxy && secondary.is_proxy ? "high" : "medium", }, ipbot, secondary, };}Best Practices
1. Don’t Block Blindly
- Legitimate users use VPNs for privacy
- Implement CAPTCHA instead of hard blocks
- Allow known users to bypass VPN checks
2. Log and Monitor
function logSuspiciousActivity(ip, reason) { console.log({ timestamp: new Date().toISOString(), ip, reason, userAgent: navigator.userAgent, }); // Send to monitoring system}3. Rate Limit Separately
// Stricter limits for proxiesconst rateLimits = { standard: 100, // requests per hour vpn: 30, proxy: 20, tor: 5,};
function getRateLimit(ipData) { if (ipInfo.is_tor) return rateLimits.tor; if (ipInfo.is_proxy) return rateLimits.proxy; if (ipInfo.is_vpn) return rateLimits.vpn; return rateLimits.standard;}4. User Communication
Be transparent about why you’re blocking:
<div id="blocked-message"> <h2>Access Restricted</h2> <p> We detected that you're connecting via a VPN or proxy service. For security reasons, please disable it and try again. </p> <p>If you believe this is an error, please contact support.</p></div>Testing Your Detection
// Test with known IPsconst testIPs = { googleDNS: "8.8.8.8", // Not a proxy knownTor: "185.220.101.1", // Tor exit node knownVPN: "104.28.1.1", // Cloudflare (may flag)};
async function testDetection() { for (const [name, ip] of Object.entries(testIPs)) { const result = await checkProxyStatus(ip); console.log(`${name} (${ip}):`, result); }}Common Questions
Are all VPN users malicious?
No. Many legitimate users use VPNs for:
- Workplace security
- Privacy protection
- Accessing content while traveling
- Circumventing censorship
Consider implementing graduated responses rather than blanket blocks.
Can users bypass proxy detection?
Yes, determined users can:
- Use private VPN services not in databases
- Rotate through multiple IPs
- Use residential proxy services
Combine IP detection with behavioral analysis for better results.
Should I block all Tor users?
Blocking all Tor users prevents legitimate privacy-conscious users from accessing your service. Consider:
- CAPTCHA challenges
- Rate limiting
- Requiring account verification
- Allowing read-only access