Guides · Updated 2026-10-20

How to detect VPN, proxy, datacenter and Tor traffic

How IP intelligence tells a person on home broadband from a server, what each signal means, and how to respond without blocking real users.

The short answer: look up who owns the IP address's network. Home and mobile providers mean a person; clouds, hosting companies and VPN-heavy networks mean a server; the Tor exit list tells you about Tor. Treat server traffic as a reason for extra verification, not an automatic block.

Every IP belongs to a network

The internet is made of autonomous systems (ASNs), each run by one organization: a broadband company, a mobile carrier, a cloud like AWS, a hosting company like Hetzner. The owner tells you far more than the location:

Network kind Example Typical traffic
ISP (home, mobile, business) Comcast, Vodafone Real people
Cloud AWS, Google Cloud, Azure Scripts, scrapers, servers, some VPNs
Hosting OVH, Hetzner, DigitalOcean VPN exits, proxies, bots
VPN-heavy hosting M247, Datacamp Commercial VPN services
CDN / relay Cloudflare, Akamai Privacy relays such as WARP, iCloud Private Relay
Tor Exit relays Anonymous browsing

Browse the network directory to see how specific ASNs are classified.

What you can and can't detect

  • Datacenter and cloud IPs: reliably. The owner is public.
  • Commercial VPNs: mostly. They run on hosting networks, which are flagged. A VPN running on a home connection (rare) looks like a home user.
  • Tor: reliably. The Tor Project publishes its exit list; Veqen refreshes it hourly.
  • Residential proxies: not by network. They route through real home connections. Pair IP checks with email and phone checks to catch these.

Code

curl https://veqen-app.com/v1/ip/check \
  -H "Authorization: Bearer $VEQEN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"ip": "185.220.101.1"}'
app.set("trust proxy", 1); // so req.ip is the visitor, not your load balancer

app.post("/signup", async (req, res) => {
  const ipCheck = await fetch("https://veqen-app.com/v1/ip/check", {
    method: "POST",
    headers: { Authorization: `Bearer ${process.env.VEQEN_API_KEY}`, "Content-Type": "application/json" },
    body: JSON.stringify({ ip: req.ip }),
  }).then((r) => r.json());

  const { hosting, tor, vpn_likely } = ipCheck.flags ?? {};
  if (tor) return res.status(403).send("Please turn off Tor to create an account.");
  req.session.needsVerification = Boolean(hosting || vpn_likely);
  // ...create the account
});

Getting the right IP

The most common mistake is checking your own load balancer's IP. If your app sits behind a proxy, use the visitor address the proxy adds:

  • Express: app.set("trust proxy", 1) then req.ip.
  • Django: read HTTP_X_FORWARDED_FOR only if your proxy sets it, and take the first address.
  • Cloudflare: the CF-Connecting-IP header.

If Veqen returns "type": "private" with the non_public_ip reason, you're sending an internal address.

Frequently asked questions

Should I block all VPN users?

Usually not. Many legitimate users have a VPN on by default. Use it as one signal, and ask for email or phone verification.

Why does iCloud Private Relay show as a relay, not a VPN?

Relays are run by Apple through CDN networks and mainly protect ordinary Safari users. They're low risk, so they carry a small impact (20).