DEV Community

Cover image for Which Proxy & VPN Signals Should Block a Signup?
ABDULLAH AFZAL
ABDULLAH AFZAL

Posted on

Which Proxy & VPN Signals Should Block a Signup?

A proxy or VPN flag at signup is a signal, not a verdict. This guide maps each IP signal to a block, friction, or allow decision at account creation, so a single boolean does not end up quietly blocking real customers or waving real fraud through.

The mistake that runs through most fake-account defenses is treating "this IP is anonymized" as "this account is fraud." It isn't. A commercial VPN, a datacenter proxy, a residential proxy, a Tor exit, and an iCloud Private Relay connection are five different risk profiles, and two of them are things your best paying customers use every day. The decision you actually make at signup is per-signal, and it has three settings: allow, add friction, block.

TL;DR

  • Most anonymized traffic at signup earns friction, not a block. Reserve outright blocks for bots, known attackers, and stacked high-risk signals.
  • Never treat a relay as a VPN. iCloud Private Relay and Cloudflare WARP are widely used privacy services, and blocking their exit IPs outright can reject legitimate users.

  • Residential proxies are the hard case. The IP genuinely belongs to a consumer ISP, so IP data alone can flag it but cannot cleanly clear it. Pair it with non-IP signals before you block.

  • Use a single 0-100 threat score to pick the action, and per-detection confidence scores to decide how far to take it.

  • Run shadow mode first: log the score and the action you would have taken for two weeks, then set thresholds against real numbers, not guesses.

A proxy flag tells you an IP is anonymized; it does not tell you the person behind it is a fraudster. The job at signup is to route each signal to the right action. Bots and known attackers get blocked, VPNs and relays usually get allowed or lightly challenged, and residential proxies get friction until a second signal confirms intent. The rest of this is the per-signal matrix and the thresholds behind it.

The signup decision matrix

This is the reference. Each row is a signal you get back on an IP lookup, the action it usually justifies at account creation, and the legitimate user you would wrongly hit if you blocked on that signal alone.

Signal Usual action at signup Why Who a hard block wrongly hits
is_bot, is_known_attacker Block Automation or a history of malicious activity. There is almost no legitimate signup reason to be either. Rare. An occasional security scanner or a recycled IP with stale reputation.
is_tor Step-up or block, by product Strong anonymity with a high abuse rate. Fine to allow for a privacy tool, wrong to allow for a payments product. Privacy-conscious users, journalists, people in censored regions.
is_residential_proxy Friction or step-up. Rarely an IP-only block. The address belongs to a real consumer ISP, so you cannot clear it on IP data alone. A real household running a proxy SDK they forgot they installed; a shared home IP.
is_proxy (datacenter or commercial) Friction; block on high confidence plus a raised score Server-origin traffic at a human signup is unusual, but not always fraud. A user behind a corporate egress that routes through a datacenter proxy.
is_vpn Friction, not a block Extremely common among legitimate privacy and corporate users. Everyone on NordVPN, Surfshark, or a work VPN. That is a lot of people.
is_relay (iCloud Private Relay, WARP) Allow or log. Do not treat as a VPN. A privacy service used by legitimate Apple users, with no inherent indication of fraud. iPhone users who have iCloud Private Relay enabled.
is_cloud_provider Context. Friction only when paired with another flag. Could be a scraper, could be a user on a cloud-hosted VPN. Developers and privacy users on cloud egress.
is_spam Friction or step-up A spam history is a moderate signal, not a conviction. Shared or recycled IPs that inherited a bad reputation.

The pattern to notice: only two rows are block-by-default. Everything else is friction or context, because the false-positive cost is real and the signals overlap. The same IP can be flagged VPN, proxy, and residential proxy at once, and the right response is driven by the combination and the score, not by any single true.

What each signal actually means at signup

Bots and known attackers

These are the clean blocks. is_bot marks automated behavior; is_known_attacker marks an IP with a history of brute force, credential stuffing, or vulnerability scanning. A human creating one account has no reason to trip either. If you block on anything at the IP layer, block on these, and log the rare false positive so you can spot a mislabeled range.

Tor exit nodes

is_tor is a product decision more than a fraud decision. Tor carries a high abuse rate, but it is also how some people access the internet safely. A privacy-forward tool might allow Tor signups with email verification. A payments product or a marketplace with promo credits probably sends Tor to step-up verification or blocks it. Pick the policy that matches what a fraudulent Tor signup would actually cost you, and say so in your own docs so support can explain it.

Residential proxies

This is the category that breaks blocklist-only defenses. A residential proxy routes traffic through egress points on real end-user devices whose IPs sit in address space allocated to consumer ISPs (security research on the residential-proxy ecosystem describes the model in detail). To your signup form, the request looks like an ordinary household on Comcast or BT, because at the network layer it is one.

That has a hard consequence: IP data can raise a flag on a known residential-proxy egress, but it cannot clear a residential IP the way it can clear a clean datacenter range, because the address is legitimately residential. So is_residential_proxy earns friction and a second look, not an automatic block. The second look is where non-IP signals carry the weight: email age, device consistency, and signup velocity from the same fingerprint. Anyone selling you "100% residential proxy detection" from IP data alone is overselling; the honest version is high-confidence flagging plus behavioral corroboration.

Datacenter and commercial proxies

is_proxy without the residential flag usually means a datacenter or commercial proxy: server-origin traffic. A person filling out a signup form from a datacenter IP is unusual enough to justify friction, and a high proxy_confidence_score stacked with a raised threat score is a reasonable block. The caveat sits right next to the rule: some corporate networks egress through commercial proxies, so confidence and score matter more than the bare boolean.

Commercial VPNs

is_vpn is the signal teams most often over-weight. Tens of millions of people run a VPN by default, for privacy, for work, or because their browser bundles one. Blocking VPN signups outright is a conversion tax you pay to catch a thin slice of fraud that better signals would catch anyway. Friction is almost always the right call: allow the signup, add a verification step, and let behavior downstream decide. Provider attribution helps here. A corporate VPN and a scraping network both set is_vpn, and naming the provider lets you write different rules for each instead of the same blunt block.

Relays: the false positive to stop making

If you take one thing from this guide, take this. is_relay covers iCloud Private Relay and services like Cloudflare WARP, and it is not a VPN in any sense that matters for fraud. Private Relay is available to iCloud+ subscribers who choose to enable it, which is a large share of iPhone traffic.

Treating relay traffic as VPN traffic and blocking it means turning away privacy-conscious Apple users at the door, and it is a common source of avoidable false positives. A separate relay flag exists precisely so you can allow these users while still challenging real VPN and proxy traffic. Allow, or log and move on.

Cloud provider IPs

is_cloud_provider on its own is weak. It could be a scraper running on a rented box, or a legitimate user whose VPN happens to exit through a cloud region. Treat it as context that sharpens other flags rather than a trigger on its own. Cloud plus bot plus a raised score is a block; cloud alone is a log line.

Turning scores into thresholds

Per-flag rules get you most of the way. A single aggregate score gets you a clean decision boundary. A composite threat_score from 0 to 100 rolls every signal into one number, and the documented action bands map directly onto the allow, friction, block model:

Threat score Meaning Signup action
1-19 Low observed risk Allow with standard controls, log for pattern analysis
20-44 Use with context Combine with device, behavior, velocity, or geo before acting
45-79 Elevated risk Add friction: OTP, CAPTCHA, email verification, rate limiting, or a review queue
80-100 High risk Block, hard challenge, or route to manual review

Two scores, two jobs. The threat_score picks the action band. The per-detection confidence scores, vpn_confidence_score and proxy_confidence_score, tell you how far to take that action. Medium-confidence VPN traffic in the 45-79 band gets a light challenge; a 99-confidence proxy detection stacked with attacker history in the 80-100 band gets blocked.

Flowchart mapping proxy flags and threat score bands to log, friction, step-up, or block actions

Here is the decision as code. It checks the strongest block signals first, allows relay traffic when no stronger risk signal is present, and then maps the remaining score bands to an action. It fails open, because a lookup timeout should not lock legitimate users out of your signup:

// Node.js 18+
const API_KEY = process.env.IPGEO_API_KEY;

// Returns "allow" | "friction" | "block". Fails open on any error.
async function signupAction(ip) {
  try {
    const res = await fetch(
      `https://api.ipgeolocation.io/v3/security?apiKey=${API_KEY}&ip=${encodeURIComponent(ip)}`,
      { signal: AbortSignal.timeout(1500) } // don't hang the signup flow
    );

    if (!res.ok) return "allow"; // fail open on API errors

    const security = (await res.json())?.security;
    if (!security) return "allow";

    const score = security.threat_score ?? 0;

    // Strong risk signals take precedence, even when the IP is also a relay.
    if (security.is_bot || security.is_known_attacker) return "block";

    if (score >= 80) return "block";

    // Relay alone is not a reason to challenge a legitimate signup.
    if (security.is_relay) return "allow";

    if (score >= 45) return "friction"; // OTP, CAPTCHA, email verification

    if (score >= 20 && security.is_residential_proxy) {
      return "friction";
    }

    return "allow";
  } catch {
    // Timeout, network error, or malformed response: let signup continue.
    return "allow";
  }
}
Enter fullscreen mode Exit fullscreen mode

The fail open choice is deliberate and worth stating explicitly: if the risk lookup is unavailable, blocking every signup is worse than letting a few risky ones through to a later layer. If your product genuinely needs fail-closed at signup, that is a real choice too, but make it on purpose.

The false-positive costs you're trading against

Every block threshold buys fraud reduction with some rate of wrongly rejected real users. You should know who those users are before you set the dial, so this is the qualitative side of the ledger:

  • The corporate VPN user. Signs up from a work laptop that routes everything through the company VPN. Sets is_vpn. Blocking them means rejecting exactly the professional buyers a B2B product wants.
  • The traveler. On hotel or airport wifi with a personal VPN switched on, because that is the responsible thing to do on public networks. Blocking them punishes good security hygiene.
  • The iPhone user. Has iCloud Private Relay enabled, so their traffic sets is_relay. If your rules fold relay into VPN, you risk blocking a legitimate privacy-conscious user.
  • The real household behind a proxy SDK. Installed a free app that quietly enrolled their connection into a residential proxy network. Their signup is genuine even though the IP is flagged. This is why residential-proxy blocks need a second signal.

These are all realistic sources of false positives. They are ordinary people, and each one is a paying customer you turned away if a single anonymization flag is enough to block. That is the whole argument for friction over blocking on the middle-risk signals.

Where these signals come from

You get flags, scores, and provider names from an IP security lookup. IPQualityScore, Spur, and proxycheck all return proxy and VPN signals; I am using the IP Security API from IPGeolocation for the examples because it splits the relay flag out from VPN, names the provider behind a detection, and returns the 0-100 threat score the bands above are built on. Security fields are on the paid plans, starting at $19/month; the free plan returns geolocation but not the security object.

A single call to the dedicated /v3/security endpoint returns the full shape. This is a real flagged IP, not a documentation placeholder, so the payload matches what you would actually get back:

{
  "ip": "2.56.188.34",
  "security": {
    "threat_score": 80,
    "is_tor": false,
    "is_proxy": true,
    "proxy_provider_names": ["Zyte Proxy"],
    "proxy_confidence_score": 90,
    "proxy_last_seen": "2025-12-12",
    "is_residential_proxy": true,
    "is_vpn": true,
    "vpn_provider_names": ["Nord VPN"],
    "vpn_confidence_score": 99,
    "vpn_last_seen": "2026-01-19",
    "is_relay": false,
    "relay_provider_name": "",
    "is_anonymous": true,
    "is_known_attacker": true,
    "is_bot": false,
    "is_spam": false,
    "is_cloud_provider": true,
    "cloud_provider_name": "Packethub S.A."
  }
}
Enter fullscreen mode Exit fullscreen mode

Three fields do the heavy lifting for signup decisions. threat_score picks the action band. is_relay, is_residential_proxy, and the provider-name arrays let you separate the risk profiles the matrix treats differently. And proxy_last_seen and vpn_last_seen date each detection, so an exit node seen this week can be weighted differently from one last observed months ago. For batch backfills over existing users, the same data is available in bulk at up to 50,000 IPs per request.

A starter policy you can ship

You do not need to get the thresholds perfect on day one. You need to stop guessing.

  1. Shadow mode for two weeks. Log the threat_score, the flags, and the action your rules would have taken, but do not block anyone yet. This tells you how much of your real signup traffic each band actually catches.
  2. Set the block line high. Start by hard-blocking only the 80-100 band plus is_bot and is_known_attacker. That catches the worst traffic with the lowest false-positive risk.
  3. Route the middle to friction. Send 45-79 to email verification or a light challenge. Send residential-proxy flags in the 20-44 band to the same friction, not a block.
  4. Let clean relays and low scores through. Allow relay traffic when no stronger block signal is present, and allow anything under 20 with standard controls.
  5. Watch the false-block rate. The number that tells you a threshold is too tight is not caught fraud, it is support tickets from real users who could not sign up. Tune against that.

A few things that trip people up

is_proxy and is_residential_proxy are not the same decision. The first covers datacenter and commercial proxies, where a block on high confidence is defensible. The second covers consumer-ISP proxies, where an IP-only block is not, because the address is genuinely residential. Read them separately.

A high threat score is a reason to act, not a reason to block. The 45-79 band is deliberately the friction band. If you find yourself blocking there, you are trading conversion for a marginal fraud reduction that a verification step would have caught anyway.

Relay is not VPN. It keeps coming up because so many defenses get it wrong. If your rules do not have a separate branch for is_relay, add one before you touch anything else.

Shadow mode is not optional busywork. The teams that block real customers are almost always the ones that set thresholds from intuition instead of from two weeks of their own logged traffic.

Start in shadow mode, block only the top band and the clean bot signals, and send the middle to friction. Then tune against your false-block rate rather than your caught-fraud count, because the false blocks are the ones that cost you customers you will never hear from again.

Top comments (0)