Check if an IP Is a VPN, Proxy, or Tor Exit Node

Abdul Mateen
By Abdul Mateen Integration Engineer @IPGeolocation.io
Posted on October 27, 2026 | 13 min read
Check if an IP Is a VPN, Proxy, or Tor Exit Node
Back to All Tutorials

To check if an IP address is a VPN, proxy, or Tor exit node, you can use an IP security lookup or check the Tor Project's published exit list. These sources help identify known VPN and proxy addresses and confirm whether an address appears in Tor's exit-node data.

Not every IP address that hides a user's location is a threat, though. Services such as iCloud Private Relay and corporate gateways also route traffic through shared addresses. If you're checking a single IP, a browser lookup is a good place to start. If you need to check addresses automatically in your application, you can use an API.


TL;DR

Checking whether an IP is a VPN, proxy, or Tor exit takes one lookup: IPGeolocation.io's /v3/security endpoint returns is_vpn, is_proxy, is_residential_proxy, is_tor, and is_relay for any IPv4 or IPv6 address, with provider names, confidence scores, and last-seen dates where available.

  • For one address, use the lookup box on the IP Security API page or the address's /browse/ip page.
  • The Tor Project's DNS exit list answers 127.0.0.2 for a listed exit.
  • Check the visitor's real IP, not the one from your CDN or load balancer.
  • Don't block relay or corporate gateway addresses on the flag alone; many ordinary users share them.

Check one IP in your browser

You can check one IP without code:

  1. Enter it in the lookup box on the IP Security API page to see its threat score and detection flags, including VPN, proxy, residential proxy, Tor, and bot.
  2. Or open ipgeolocation.io/browse/ip/ followed by the address for twelve checks, Tor among them, with provider names when a check is positive.
  3. To see how your own connection looks from outside, live check your own IP with your VPN on and off.
  4. To learn whether an address was a Tor relay on a past date, such as the day of an incident, enter the IP and date in the Tor Project's ExoneraTor.

First, make sure you're checking the visitor's IP

Behind a CDN, load balancer, or reverse proxy, the address on the connection is the proxy's, not the visitor's, and a VPN check on it describes your own infrastructure. Use a forwarded client address only when it comes from a proxy you trust, and the connection's own address otherwise. Reading the visitor's real IP behind a proxy or CDN covers Nginx, Cloudflare, and other setups.


Check VPN, proxy, and Tor in one API call

To detect whether a user is on a VPN or proxy inside your app, send their IP to the IP Security API, which answers all three questions in one request. You need an API key on a plan with security data, stored in IPGEOLOCATION_API_KEY, and curl or Python 3.9 or later with requests. The fields parameter keeps the response to the flags and their evidence:

curl -s -G "https://api.ipgeolocation.io/v3/security" \
  -d "apiKey=${IPGEOLOCATION_API_KEY}" \
  -d "ip=155.94.186.115" \
  -d "fields=security.threat_score,security.is_anonymous,security.is_vpn,security.vpn_provider_names,security.vpn_confidence_score,security.vpn_last_seen,security.is_proxy,security.proxy_provider_names,security.proxy_confidence_score,security.proxy_last_seen,security.is_residential_proxy,security.is_tor,security.is_relay,security.relay_provider_name,security.is_corporate_gateway,security.corporate_gateway_provider_name"

On October 21, 2026, IPGeolocation.io's API connector returned (trimmed):

{
  "ip": "155.94.186.115",
  "security": {
    "threat_score": 50,
    "is_tor": false,
    "is_proxy": false,
    "is_residential_proxy": false,
    "is_vpn": true,
    "vpn_provider_names": ["Windscribe VPN"],
    "vpn_confidence_score": 70,
    "vpn_last_seen": "2026-10-19",
    "is_relay": false,
    "is_anonymous": true,
    "is_corporate_gateway": false
  }
}

It's a Windscribe VPN exit, last seen two days earlier.

In an application, wrap the call so errors can't crash the request and a missing field never reads as clean:

import logging
import os
from typing import Any, Dict, Optional

import requests

log = logging.getLogger(__name__)

API_URL = "https://api.ipgeolocation.io/v3/security"
FLAGS = (
    "is_anonymous",
    "is_vpn",
    "is_proxy",
    "is_residential_proxy",
    "is_tor",
    "is_relay",
    "is_corporate_gateway",
)
DETAILS = (
    "threat_score",
    "vpn_provider_names",
    "vpn_confidence_score",
    "vpn_last_seen",
    "proxy_provider_names",
    "proxy_confidence_score",
    "proxy_last_seen",
    "relay_provider_name",
    "corporate_gateway_provider_name",
)
FIELDS = ",".join("security." + name for name in FLAGS + DETAILS)


def check_ip(ip: str) -> Optional[Dict[str, Any]]:
    """Return the security fields for one IP, or None if the lookup is unavailable.

    A flag that is missing or isn't a boolean comes back as None (unknown), never False.
    """
    api_key = os.environ.get("IPGEOLOCATION_API_KEY")  # never hardcode the key
    if not api_key:
        # A missing key is a deployment bug, so fail loudly instead of returning None.
        raise RuntimeError("Set IPGEOLOCATION_API_KEY first.")

    try:
        resp = requests.get(
            API_URL,
            params={"apiKey": api_key, "ip": ip, "fields": FIELDS},
            timeout=5,
        )
    except requests.RequestException as exc:
        # Log the type only: the message can include the URL, which includes the key.
        log.warning("Security lookup for %s failed: %s", ip, type(exc).__name__)
        return None

    if resp.status_code != 200:
        # 401: bad key, or a free-plan, database-only, or paused subscription key.
        # 423: private or bogon IP. 429: usage or surcharge limit reached.
        log.warning("Security lookup for %s returned HTTP %s", ip, resp.status_code)
        return None

    try:
        body = resp.json()
    except ValueError:
        log.warning("Security lookup for %s returned a non-JSON body", ip)
        return None
    security = body.get("security") if isinstance(body, dict) else None
    if not isinstance(security, dict):
        log.warning("Security lookup for %s had no security object", ip)
        return None

    result: Dict[str, Any] = {"ip": ip}
    for name in FLAGS:
        value = security.get(name)
        result[name] = value if isinstance(value, bool) else None
    for name in DETAILS:
        value = security.get(name)
        result[name] = None if value == "" else value  # "" means no name or date
    # Tells the caller whether every flag came back, so a partial answer isn't read as clean.
    result["complete"] = all(result[name] is not None for name in FLAGS)
    return result


if __name__ == "__main__":
    logging.basicConfig(level=logging.INFO)
    result = check_ip("155.94.186.115")
    if result is None:
        print("Lookup unavailable: apply your fallback policy.")
    else:
        print(
            f"VPN: {result['is_vpn']}, providers: {result['vpn_provider_names']}, "
            f"last seen: {result['vpn_last_seen']}, complete: {result['complete']}"
        )

With the October 21 response, the script prints:

VPN: True, providers: ['Windscribe VPN'], last seen: 2026-10-19, complete: True

A missing key raises an error, because it's a deployment bug. A failed lookup returns None, meaning "no answer", not "clean"; an omitted flag stays None and sets complete to False. An empty provider list means a known exit with no named provider.

Handling "no answer" is its own decision: fail open (proceed, fine for reading content), fail closed (refuse until a check succeeds), manual review (accept but hold for a person), or fall back to another source, such as a cache, a local database, or the Tor DNS list.

For logs and batch jobs, /v3/security-bulk takes up to 50,000 addresses per request.

curl -s -X POST "https://api.ipgeolocation.io/v3/security-bulk?apiKey=${IPGEOLOCATION_API_KEY}&fields=security.is_vpn,security.is_proxy,security.is_residential_proxy,security.is_tor,security.is_relay" \
  -H "Content-Type: application/json" \
  -d '{"ips": ["155.94.186.115", "185.220.100.240", "87.58.66.106"]}'

Only valid addresses are billed. Private, bogon, and malformed entries return just a message, with no ip, so join results to inputs by position (the IP Security API page says order is kept) and check each ip before storing it. Offline? The same data ships as the IP Security Database.


Verify a Tor exit with the Tor Project's own data

You can verify a Tor match yourself, because the Tor network publishes its exits. A Tor exit node is the last relay in a Tor circuit, the address a website sees when someone uses Tor. Four Tor Project sources answer slightly different questions:

  1. The bulk exit list at check.torproject.org/torbulkexitlist, one address per line. The Tor Project says it's updated at most once every 40 minutes and that fetching it hourly is reasonable, so cache it and check against your copy.
  2. The DNS exit list. Reverse an IPv4 address's four numbers and query <reversed IP>.dnsel.torproject.org. A listed exit answers 127.0.0.2, an unknown one NXDOMAIN, and a TXT query returns each relay's fingerprint. A timeout or server error is neither answer.
  3. Onionoo, the relay database behind Tor Metrics. Its exit_addresses field lists the IPv4 addresses a relay exited from in the past 24 hours, plus the operator's AS and contact.
  4. ExoneraTor, for a past date. It stores a relay's exit address as well as the address it registers with, so the IP in your logs still matches.
Decision diagram: DNS exit list for one IP now, bulk list for many IPs, ExoneraTor for a past date, Onionoo for details.

Here's 185.220.100.240, an exit run by the German association F3 Netze e.V., checked against the DNS list on October 21, 2026:

dig +short 240.100.220.185.dnsel.torproject.org
dig +short 240.100.220.185.dnsel.torproject.org TXT
127.0.0.2
"3C89C80E2699FB6358BBB64FDC9547AFCB5C03F7"
"8E38B11E849336D62AB6264FBAE9B03C70F07863"

Two fingerprints mean two relays share the address, and Onionoo lists both, named F3Netze, on ports 9000 and 9100. It was in that day's bulk list too, and our API returned is_tor: true.

The same check in Python, with only the standard library:

import ipaddress
import socket
from typing import Optional


def is_tor_exit(ip: str) -> Optional[bool]:
    """Ask the Tor Project's DNS exit list: True, False, or None if unknown."""
    addr = ipaddress.ip_address(ip)
    if addr.version != 4:
        return None  # this function builds the IPv4 query only; None means "not checked"
    query = ".".join(reversed(str(addr).split("."))) + ".dnsel.torproject.org"
    try:
        answer = socket.gethostbyname(query)
    except socket.gaierror as exc:
        if exc.errno == socket.EAI_NONAME:
            return False  # NXDOMAIN: not a listed exit at query time
        return None  # resolver trouble means "unknown", not "clean"
    return True if answer == "127.0.0.2" else None  # any other answer is unexpected


print(is_tor_exit("185.220.100.240"))  # True on October 21, 2026
print(is_tor_exit("155.94.186.115"))   # False on October 21, 2026

The sources describe different moments, so they can disagree without either being wrong: a DNS answer is current, a bulk copy is as old as its download, an API flag is as fresh as the provider's last refresh, and ExoneraTor answers for the date you give it. Trust the most recent observation for the present and ExoneraTor for an incident. On October 21, 2026, two bulk-list downloads held 1,208 and 1,221 addresses, all IPv4, and Onionoo's exit_addresses are IPv4 too, though relays can listen on IPv6; an IPv6 address that matches no list is unchecked, not cleared.


Why VPN, proxy, and Tor answers carry different weight

A Tor flag, a VPN flag, and a residential proxy flag come back in the same response, but they rest on different kinds of evidence, and a practical way to weigh them is by how public that evidence is.

Tor exits are published, VPN exits enumerated, and residential proxies share IPs with households, so certainty falls.
The less public the evidence, the more an IP-level answer needs a second signal.

Tor exits are published. A match is close to a fact about the address as of the list's last update, but an exit carries traffic from everyone using it, so it says nothing about who sent this request.

VPN exits are found by enumeration. Most commercial VPNs don't publish their exits, so detection data comes from connecting through each provider and recording the exits it uses, which is why a VPN flag can carry a provider name, a confidence score, and a last-seen date. An exit seen this week is stronger evidence than one seen months ago, and a new or self-hosted server may not be listed yet. How VPN detection works covers the methods.

Residential proxies share addresses with households. A dataset can say an address belongs to a residential proxy network, but it can't tell whether this request came through the proxy or from the household itself. That's why residential proxies are hard to detect, and it's where an IP-level answer is weakest. Real-time VPN and proxy detection adds a second signal by testing the live browser session, though no method catches every residential proxy.


How to read the result

Read the type flags first, then the fields that show how strong and how fresh the evidence is:

IPGeolocation.io security response for a Tor exit, with callouts on type, provider, confidence, and last seen.
Field What it means Read next
is_anonymous True if a VPN, proxy, Tor, or relay was detected; corporate gateways don't set it One of those four flags set it
is_vpn The address is linked to a VPN network vpn_provider_names (can be empty), vpn_confidence_score (0 to 100), vpn_last_seen
is_proxy The address is linked to a proxy network proxy_provider_names, proxy_confidence_score, proxy_last_seen
is_residential_proxy The address is linked to a residential proxy network The proxy fields, and a live session test if the action matters
is_tor The address is a known Tor exit node The Tor Project's DNS list, for a current second source
is_relay The address is linked to a relay network such as iCloud Private Relay relay_provider_name
is_corporate_gateway The address is egress for a secure web gateway, SSE, or SASE service corporate_gateway_provider_name, corporate_gateway_type
is_cloud_provider The address belongs to a cloud provider cloud_provider_name; a hint about the network, not a verdict
threat_score A composite score from 0 to 100; higher means higher risk The flags behind it

Confidence scores read 0 when their flag is false, and provider names and last-seen dates appear only when known.

Two lookups from October 21, 2026 show why the type matters more than the number. The Windscribe exit scored 50: a named commercial VPN, seen two days earlier, on HostPapa hosting. The Tor exit at 185.220.100.240 scored 100 and also carries is_known_attacker, is_spam, and is_bot with bot_type: "scanner". Those flags describe the address, not its current user; many people share an exit, so abuse from some of them attaches to it.

The threat score folds honeypot and blocklist hits, VPN and proxy enumeration, and attacker, bot, spam, and hosting signals into one number. It isn't a probability of fraud, and it blends what the flags keep apart: anonymity and the address's own abuse record. The IP Security API page's starting bands run from "allow with standard controls" at 1 to 19 to "block, hard challenge, or route to manual review" at 80 to 100, for you to tune. Whether to act on this request also depends on the action and the account.


Flags that look like anonymizers but usually aren't

Some addresses hide the person behind them for ordinary reasons, and the response separates them if you read the right fields.

On October 21, 2026, 172.225.7.142, an iCloud Private Relay address from Apple's published egress list, returned is_relay: true, relay_provider_name: "Apple Relay", and a threat score of 50, with is_vpn and is_proxy both false. By score alone, it looks like the Windscribe exit; the relay flag tells them apart. Apple says a relay address may be shared among more than one Private Relay user and suggests treating these addresses like large carrier-grade NAT or enterprise addresses. The comparison shows how Private Relay and WARP show up in detection data.

Corporate secure web gateways such as Zscaler send a whole workforce's browsing through a few addresses. The same day, 87.58.66.106 returned is_corporate_gateway: true, corporate_gateway_provider_name: "Zscaler", is_anonymous: false, and a threat score of 5; the IP Security API page says the gateway flag doesn't increase the threat score. A check that only asked "is this a cloud address?" would have flagged it.

Mobile carriers and carrier-grade NAT place many customers behind one address, so flagging a single customer can affect everyone sharing it. Consider the request's other signals before adding friction. Hosting addresses can change hands, and the last-seen fields indicate how old the evidence is.


What to do when an IP checks positive

Decide by the type of match and the action at stake, and weigh the flag with the account's history, request velocity, device signals, and payment risk. Our starting recommendations:

  • VPN: challenge rather than block, since many people use one for privacy or on public Wi-Fi. Ask for MFA, an email code, or a CAPTCHA at signup and login, and save hard blocks for actions a VPN user has no ordinary reason to take, such as a region-locked purchase.
  • Datacenter or commercial proxy (is_proxy: true, is_residential_proxy: false): the strongest of these signals at signup and checkout, where scrapers and card-testing scripts use them and ordinary customers rarely do. Hold for review or block.
  • Residential proxy: don't block on the flag alone, because the household behind it may be your customer. Pair it with a live session test or behavioral signals first.
  • Tor: treat the address as shared and high-abuse, not as proof about the person. Restrict payments, password resets, and account changes; allow reading, and consider offering an onion service, as the Tor Project suggests.
  • Relay or corporate gateway: treat the address as shared and never as a user identity. If you loosen per-IP rate limits for it, keep per-account and per-device limits in place.

Check VPNs, proxies, and Tor with IPGeolocation.io

IPGeolocation.io's IP Security API detects VPNs, proxies, residential proxies, Tor exits, and privacy relays, names the provider where known, and adds confidence scores and last-seen dates to its VPN and proxy detections. It's the most complete anonymity answer in a single call that we know of: it separates relays and corporate gateways from real anonymizers and adds attacker, bot, and spam signals, so a Zscaler office, an iPhone on Private Relay, and a scraping proxy can each get their own rule. How we detect VPNs, proxies, and Tor explains each detection method.

Security is one part of the IP Geolocation API, which returns geolocation, security, ASN, company, abuse contact, time zone, and user agent data; include=security on /v3/ipgeo adds the security object to a full lookup. It also ships as the IP Security Database and Residential Proxy Database for offline use, alongside a real-time script for the live session. See pricing for what each plan includes.

Frequently asked questions

Usually, yes. An IP security dataset, such as IPGeolocation.io's, flags known VPN exit nodes and may include the provider and last-seen date. A negative result means the IP isn't known to be a VPN; new or self-hosted servers may not appear until they've been observed.

Usually because the address is shared or recycled. Your carrier may put many customers behind one address, your employer may route traffic through a secure web gateway, or the address may have previously served as a proxy or VPN. Last-seen dates and the gateway and relay flags separate these cases from an active proxy.

The Tor Project publishes it at check.torproject.org/torbulkexitlist, a plain text file updated at most once every 40 minutes. For one address in real time, query dnsel.torproject.org, which answers 127.0.0.2 for a listed exit.

A Tor exit node is the last relay in a Tor connection: it passes traffic on to the website, which sees the exit's IP address instead of the user's.

Not reliably from the IP alone, because a residential address also carries the household's own traffic. A live session test, such as IPGeolocation.io's real-time VPN and proxy detection, adds a signal from the connection itself, which is why residential proxies are hard to detect.

For occasional checks, yes: the lookup box on IPGeolocation.io's IP Security API page, the /browse/ip/ pages, and the Tor Project's lists are free. In code, the IP Security API is on paid plans, starting at $19 a month for 150,000 credits on the Starter plan; each security lookup uses 2 credits; see pricing and the credits each lookup uses.


Where to go next

Try the address you were concerned about in the lookup box on the IP Security API page. Then add `check_ip` to the action where a fake account or stolen card would cost you the most. For production use, get an API key and compare the pricing plans.

Related Tutorials

Show Prices in Local Currency Based on IP
Show Prices in Local Currency Based on IP

Detect each visitor's currency with one IPGeolocation.io lookup, convert your base price with daily exchange rates, and format it for their locale. Runnable Node.js and Express code, with a switcher that always wins.

Posted on October 22, 2026
Read More
What Is My IP API? Get the Caller's Public IP with getip
What Is My IP API? Get the Caller's Public IP with getip

Get your public IP from any script, server, or browser with IPGeolocation.io's getip endpoint. No API key or credits, and tested code for cURL, Python, Node.js, Go, and PHP.

Posted on October 21, 2026
Read More
How to Add Geolocation to Your Shopify Store
How to Add Geolocation to Your Shopify Store

How to route Shopify visitors to the right market by country, and how to decide whether you need a geolocation app or Shopify's built-in redirect.

Posted on July 14, 2026
Read More
How to Get IP Geolocation in JavaScript (Node + Browser)
How to Get IP Geolocation in JavaScript (Node + Browser)

Get IP geolocation in JavaScript the right way: the official SDK in Node.js, the correct browser pattern without exposing your key, the visitor's real IP via Express and Next.js, and the location, timezone, currency, ASN, VPN, proxy and threat data the API returns.

Posted on June 24, 2026
By Mudassar Tariq
Read More
How to Get IP Geolocation in PHP (API Tutorial)
How to Get IP Geolocation in PHP (API Tutorial)

Get IP geolocation in PHP with the official IPGeolocation.io library: install with Composer, run your first lookup, and read the real visitor IP in vanilla PHP and Laravel. Links to optional VPN/proxy, abuse contact, and bulk lookups on paid plans.

Posted on June 22, 2026
By Mudassar Tariq
Read More
How to Get IP Geolocation in Python (API Tutorial)
How to Get IP Geolocation in Python (API Tutorial)

Get IP geolocation in Python with the official IPGeolocation.io library: single lookups, async requests, and real visitor IP handling in Flask and FastAPI, plus optional bulk lookup and VPN/proxy detection examples.

Posted on June 19, 2026
By Mudassar Tariq
Read More

Subscribe to Our Newsletter

Get the latest in geolocation tech, straight to your inbox.