Welcome to the Hangar Open Beta. Please report any issue you encounter on GitHub!
Avatar for LucasTHCR

Anti-VPN and anti-proxy protection with country filtering, Discord alerts, a silent mode and a dry-run mode.

Report ProxyShield?

Discord Release Downloads Servers License

Built with Java 21 Available for Paper Available for Purpur Available for Spigot Available for Bukkit Available for Folia Available for Sponge Available for BungeeCord Available for Waterfall Available for Velocity

Chat with us on Discord Available on Codeberg

bStats

ProxyShield

Blocks VPNs, proxies, Tor exits and datacenter ranges at login — by asking several services at once and adding up what they say, instead of trusting one of them.

That difference is the whole plugin. Every anti-VPN queries an API and kicks on proxy: true. The problem is what happens when that one service has never heard of the VPN in front of it: it answers "clean", nothing else is asked, and the connection goes through. The list of VPNs any single free dataset misses is long, and it is exactly the list of VPNs people actually pay for.

ProxyShield asks all of them, weighs the answers, and refuses the connection once enough of them agree. One blind provider can no longer wave a VPN through, and one noisy provider can no longer kick your players on its own.

Build Runs on
ProxyShield-Paper-2.2.1.jar Paper, Purpur, Pufferfish and plain Spigot 1.21+
ProxyShield-Folia-2.2.1.jar Folia
ProxyShield-Bungee-2.2.1.jar BungeeCord / Waterfall
ProxyShield-Velocity-2.2.1.jar Velocity 3.3+
ProxyShield-Sponge-2.2.1.jar SpongeAPI 17

Install one jar, on the machine that accepts the connection. On a network that is the proxy — a Paper build behind BungeeCord only ever sees the proxy's own address. The Paper and Folia jars are built from the same sources and are interchangeable; both exist so people find a jar named after their platform.

Drop it in, restart, done. Seven providers are enabled out of the box and none of them needs an API key.


How a verdict is reached

Every enabled provider is queried at the same time. Each one carries a weight, the answers are added up, and the connection is refused once the total reaches required-score. At the default of 1.5, one strong provider is enough on its own, or any two weaker ones, or one weak provider plus a reverse-DNS hit.

api:
  mode: consensus          # or failover
  required-score: 1.5
  providers:
    ip-api:     { enabled: true, weight: 1.0 }
    blackbox:   { enabled: true, weight: 1.0 }
    ipapi-is:   { enabled: true, weight: 1.5 }
    proxycheck: { enabled: false, weight: 2.0, key: "" }

Raise the score to 2.5 if you would rather miss a VPN than kick a real player. Lower it to 1.0 to block on any single hit, which is what most other anti-VPN plugins do by default.

mode: failover keeps the old behaviour — ask in order, first answer wins. It costs fewer requests and detects less.

This does not multiply your quota the way it sounds. Answers are cached, simultaneous logins from one address share a single lookup, the fan-out stops the moment the score is reached, the cache survives restarts and reloads, and a provider that starts failing is dropped from the rotation until it recovers. In practice a busy server spends fewer requests than v2.1 did, not more.

26 services, 15 of them free

Enabling one is a single line. Each entry knows its own response layout, so nothing is guessed at.

No key needed: ip-api, blackbox, iprisk, ipquery, ipapi-is, iplocate, freeipapi, funkemunky, negativity, fastasfuck, zombiebot, rayzs, marvinmc, ip-check, zowi

Key based: proxycheck, ipqualityscore, vpnapi, iphub, iphunter, ip2location, vpnblocker, getipintel, antivpn-io, 2ip, abuseipdb

A service that needs a key stays inert until you add one, and says so once at start-up rather than failing on every login. Anything not in the list can be described directly in config.yml with a URL and dotted field paths — no code, no waiting for an update.

/proxyshield providers shows every enabled service with its weight, health and latency. /proxyshield check <ip> shows what each one said about a specific address, which is what you want open in front of you when somebody reports a false positive.


The signals that cost nothing

APIs are lists of addresses somebody already reported. An attack from a range rented this morning is invisible to all of them — but not to the server being attacked.

Network (ASN) rules. Address feeds always lag: a VPN rents a new range and the public lists catch up days later. The network number behind it does not change nearly as often, so blocking the ASN stops every address inside it at once. /proxyshield lookup <ip> prints the number to put in the list.

Reverse DNS. Hosting and VPN operators name their machines after themselves, so a PTR record like mad-01.torguard.io gives it away for free, with no quota and no rate limit. It adds weight rather than blocking outright, because plenty of ordinary ISPs put words like host in their customer names. Runs under a hard timeout so a slow resolver cannot stall a login.

Behaviour. Thirty accounts appearing out of one /24 inside a minute is a shape a normal player base does not produce. So is one address cycling through a dozen names, or an account whose country changes between two logins ten minutes apart. These add weight too, so a school or a Discord community joining together is not a mass kick.

Tor exit list. The most reliable feed there is — those addresses are published by the network itself.


Not kicking real players

The cost of an anti-VPN is not the VPN it misses. It is the regular who gets thrown out on a Friday evening and does not come back.

Trust. An account that has connected cleanly often enough, over long enough, stops being re-judged on every login. Mobile players behind carrier-grade NAT are the ones a hosting block hits hardest — their address can sit in a flagged range one evening and not the next — and this is what keeps them online. It never overrides your manual blacklist, and it is reset the moment an account is caught behind a proxy.

Connection type. Where a provider reports that an address is residential or mobile, the hosting block does not apply to it. Carriers land in datacenter ranges more often than you would think.

Dry-run. detection.dry-run: true reports everything and disconnects nobody. Run it for a day, read blocks.log, then switch enforcement on knowing what it would have done.

Silent mode. No staff broadcast and no console line, while the log file and the Discord webhook keep recording. Watch the filter without announcing every detection to everyone online.


On a network

One server needs no setup — verdicts and player history live in two files next to the config.

A network of several proxies should share them, so each address is paid for once for the whole network instead of once per proxy, and every front end agrees about it:

storage:
  type: redis            # or mysql, mariadb, postgresql, sqlite, h2, file, memory
  redis:
    host: "127.0.0.1"
    port: 6379
    password: ""

Redis also pushes: whitelisting an address or running /proxyshield clearcache on one proxy takes effect on the others immediately, rather than whenever the entry happens to expire.

No database driver and no Redis library is bundled, so nothing is relocated into a server that did not ask for it. Most Bukkit servers already have a MySQL and a SQLite driver; if yours does not, drop the driver jar into the ProxyShield folder and name it under storage.sql.driver-path.

If the store cannot be reached, the plugin says so once and falls back to file storage. A database outage never becomes a login outage.


Order of decision

Strictly top to bottom, first match wins. Everything that can allow a player is evaluated before anything that can block one, so you can always whitelist your way out of a false positive.

# Check Result Cost
1 Bypass permission allow none
2 Bedrock player (Geyser/Floodgate) allow none
3 Name on the player whitelist allow none
4 IP on the whitelist allow none
5 Loopback or private address allow none
6 IP on the blacklist block none
7 Trusted regular allow none
8 IP in a downloaded feed block ~0.6 µs
9 Network on the ASN block list block shared with 10
10 Providers, reverse DNS and behaviour reach the score block one round per uncached address
11 Country not permitted block same round

Steps 1 to 8 never touch the network.

Bedrock players are exempt by default. Wherever Geyser runs anywhere other than the player's own machine, the address seen here is Geyser's — and a Geyser instance on rented hardware sits in exactly the datacenter ranges the feeds list, so without that exemption a server would drop every Bedrock player the moment hosting detection was switched on.


Performance

Measured with the default feeds, 62,555 entries compiled into 35,161 merged ranges:

  • Memory held — 1.3 MB
  • Startup parse — 259 ms, on a background thread
  • Feed check per login — 589 ns
  • 1000 logins — 0.59 ms total, about 1/85th of one tick

The provider round is the only part with real latency. It runs off the main thread on every platform, so it delays that one player's login and never the server tick, and it has a hard ceiling (api.total-timeout-seconds) — whatever answered by then decides. Set use-api: false for a feed-only setup with no network traffic per login at all.


Commands

All need proxyshield.admin. Aliases: /ps, /antivpn.

Command Does
/proxyshield info Version, lookup mode, storage and list status
/proxyshield providers Every enabled service with its weight, health and latency
/proxyshield stats Detection counters, cache, webhook and efficiency
/proxyshield check <player|ip> Runs the full pipeline and prints what each provider said
/proxyshield lookup <player|ip> Fresh lookup ignoring the cache, with ASN and score
/proxyshield whitelist <add|remove|list> [entry] Manage the never-block list
/proxyshield blacklist <add|remove|list> [entry] Manage the always-block list
/proxyshield refresh Re-download the feeds
/proxyshield recheck Re-check everyone online, disconnect whoever fails
/proxyshield clearcache Drop cached verdicts, locally and network-wide
/proxyshield reload Re-read the config and the message file

Permissions

Node Default Grants
proxyshield.bypass op Skip every check
proxyshield.notify op Receive live block alerts
proxyshield.admin op Use /proxyshield

On Paper, BungeeCord and Sponge no permission data exists yet during the login handshake, so proxyshield.bypass only resolves for operators there. For reliable exemptions on those platforms use the name or IP whitelist. On Velocity the permission works normally.


PlaceholderAPI

Available on the Bukkit builds when PlaceholderAPI is installed. Every placeholder reads the cache, so none of them can stall the main thread.

Placeholder Shows
%proxyshield_blocked% %proxyshield_checked% Counters since start-up
%proxyshield_blocked_proxy% %proxyshield_blocked_hosting% %proxyshield_blocked_country% Blocks by reason
%proxyshield_country% %proxyshield_isp% %proxyshield_asn% The player's own address
%proxyshield_is_proxy% %proxyshield_is_hosting% %proxyshield_score% The verdict on it
%proxyshield_sources% Which services flagged it
%proxyshield_trusted% Whether the player has earned an exemption
%proxyshield_mode% %proxyshield_providers% %proxyshield_storage% How the plugin is set up
%proxyshield_cache_size% %proxyshield_trusted_players% %proxyshield_uptime% Live state

For other plugins

if (ProxyShieldApi.isAvailable() && ProxyShieldApi.isProxy(address)) {
    // cache only, safe on the main thread
}

LookupResult result = ProxyShieldApi.lookup(address);   // blocking, async only
boolean regular = ProxyShieldApi.isTrusted(uuid);

Read-only: it answers questions, it never changes a verdict or disconnects anybody.


Configuration

Everything behaves identically on all five platforms and lives in one config.yml. Player facing text is separate, in messages_en.yml / messages_de.yml, so you can translate or reword anything without touching the jar.

The settings people change first

detection:
  dry-run: false               # detect and report, never kick
  block-proxy: true
  block-hosting: true
  block-tor: true
  allow-residential-hosting: true   # do not kick mobile carriers

  asn:
    enabled: true
    blocked: []                # e.g. "AS9009" - M247, used by many commercial VPNs
    allowed: []                # your own hosting, a school, a carrier

  rdns:
    enabled: true
    weight: 1.0

api:
  mode: consensus
  required-score: 1.5          # 1.0 blocks on any single hit, 2.5 is cautious
  total-timeout-seconds: 6     # ceiling for the whole lookup stage of one login

trust:
  enabled: true
  min-logins: 5
  min-days: 3

heuristics:
  enabled: true
  max-joins-per-subnet-per-minute: 12
  max-accounts-per-ip: 4
  geo-jump-minutes: 10

country:
  enabled: true
  mode: whitelist              # only these countries may connect
  codes: ["DE", "AT", "CH"]

notifications:
  silent: false                # true = log and webhook only, no live alerts

Describing a service the plugin does not know

api:
  providers:
    my-provider:
      enabled: true
      url: "https://example.com/api/{ip}?key={key}"
      key: ""
      weight: 1.0
      format: json             # json or plain
      proxy-fields: ["security.vpn", "security.proxy"]
      hosting-fields: ["is_datacenter"]
      country-fields: ["location.country_code"]
      asn-fields: ["asn.asn"]
      score-field: "risk_score"
      score-threshold: 70
      key-header: ""           # set if the key goes in a header, e.g. X-Key
      nested-by-ip: false      # true if the answer is keyed by the address

Dotted paths work, so security.vpn reads {"security":{"vpn":true}}. For a service that answers with a bare word:

    my-simple-provider:
      enabled: true
      url: "https://example.com/check?ip={ip}"
      format: plain
      true-values: ["true", "y", "1"]

Metrics

Anonymous statistics through bStats: server count, platform, Java version and which ProxyShield settings are in use. No addresses and no player data. Public at bstats.org, and switched off with metrics.enabled: false.

Support

Questions and bug reports: dc.gg/paperstream Source and issues: codeberg.org/LucasTHCR/ProxyShield

Information

Category
Protection
Published
August 17, 2026
License
13Downloads
0Stars

Pinned Versions

  • R
    Paper1.21–26.2Waterfall1.21Velocity3.3–3.5

Members

1