GeoRestrict helps Minecraft server owners decide where connections may come from. It can use country codes, network ASNs and VPN/proxy signals, while keeping recent lookup results in a local cache.
The same jar runs on Bukkit, Spigot, Paper, Purpur, Folia, BungeeCord, Waterfall and Velocity. The latest published version is v2.0.2; this checkout prepares the v2.1.0 patch. GeoRestrict targets Java 17 bytecode; use a newer Java runtime whenever your server platform requires one.
This checkout also builds separate Fabric, Forge and NeoForge dedicated-server mods for the requested Minecraft 1.20–26.3 range. See mod installation and architecture. These candidate mod builds are not published on Modrinth yet.
Sometimes a community only serves one region. Sometimes a server is being hit by one hosting network. Sometimes an admin needs a broad VPN rule for a short period, then wants to loosen it again. GeoRestrict keeps those decisions in a readable YAML file instead of hiding them in a remote panel.
- Country allowlists and blocklists using ISO two-letter codes
- ASN allowlists and blocklists for network-level control
- Configurable VPN, proxy and hosting detection
- Bounded local cache with atomic saves and expiry cleanup
- Cloudflare Worker gateway with a 24-hour Cache API tier and provider failover
- Protected fallback server that reads Country and ASN data from local MMDB files
- Discord notifications with masked IPs by default
- Manual lookup, cache, purge and reload commands
- Standard aggregate bStats metrics; no player names or IP addresses are sent to bStats
Pumpkin has a self-contained Rust/WASI plugin in adapters/pumpkin.
It uses Pumpkin's native Java and Bedrock login events and includes its own
local-address, country, ASN, VPN and cache logic. No Java admission bridge is
required. Build the WASI component, copy it into Pumpkin's plugins directory,
and configure plugins/data/georestrict/config.yml.
The native installation guide explains permissions, commands and trusted player addresses. Candidate builds are not marketplace publications.
GitHub Actions builds and tests the Java plugin, server adapters, native Pumpkin
component and gateways on pull requests and pushes to main. Pushing a matching
version tag such as v2.1.0-rc.1 runs those checks and publishes a GitHub Release
with binaries, full source, dependency notices and checksums. Incomplete or
unmatched live evidence keeps the release marked as a prerelease. Read the
release workflow guide for tag and local Git sync instructions.
- Download
georestrict-2.0.2.jarfrom Modrinth. - Put it in the server or proxy
plugins/folder. - Restart once so
plugins/GeoRestrict/config.ymlis created. - Choose
BLOCKLISTorALLOWLIST, add the countries you need, then run/georestrict reload.
The live field guide covers installation, configuration, commands, how the lookup path works, changelog, and troubleshooting.
On a plugin-cache miss, the connecting IP is sent to the configured Worker. The Worker checks its 24-hour edge cache, then tries its configured geolocation providers in order. If they all fail, the protected fallback server resolves country and ASN data from local MMDB files. GeoRestrict does not operate a player dashboard or central player log.
The Worker uses Cloudflare's Cache API by default, with a small bounded in-isolate memory cache. An optional KV binding is supported but deliberately disabled unless an operator configures it. Primary country and ASN lookups are powered by IPinfo Lite; the fallback server uses monthly DB-IP Lite Country and ASN data under CC BY 4.0.
Local addresses bypass geolocation because they have no geographic country. To reject local players, enable:
blockLocalConnections: true
kickMessageLocalConnection: "Local network connections are not allowed on this server."Alternatively, the reported country-list configuration now works:
countryMode: BLOCKLIST
countries:
- LOCAL
- CNThe LOCAL blocklist entry uses kickMessageCountry. The explicit flag works independently of country mode and uses its own message. After /georestrict reload, /georestrict check 127.0.0.1 reports Allowed: No and Country: LOCAL with either option.
Addresses are detected automatically: loopback, RFC1918 LAN, link-local, IPv6 unique-local and unspecified addresses, including mapped IPv4 and IPv6 interface zones. Other reserved ranges such as CGNAT and documentation addresses keep their prior handling. Local checks do not call the lookup gateway or populate the geo cache. Existing Bukkit operator/permission and Floodgate full bypasses still apply to joins.
Blocking defaults to false, including migrated configurations. A pre-existing LOCAL entry in a country blocklist now takes effect. On BungeeCord/Velocity networks, enable blocking at the public-facing proxy or configure trusted player IP forwarding on the backend. A backend that sees only a local proxy/Geyser address cannot distinguish its internet players from local players; leave local blocking disabled there. No fixed player IPs are required.
Most server owners do not need this section. The generated plugin config points to the public Worker and works without a private token. Do not change the Worker URL or token unless you understand and operate the replacement path.
Minecraft connection
-> plugin cache on the Minecraft server
-> Cloudflare Worker memory and Cache API
-> IPinfo Lite
-> IP-API Pro when a key is configured
-> protected fallback server using local DB-IP Country and ASN files
-> blockOnLookupFailure policy if no route returns a result
The plugin jar contains no geolocation provider URL or provider credential. It sends an uncached address only to gatewayUrl. Provider selection, retries and the final fallback route stay behind the Worker.
The production Worker declares its provider endpoints explicitly in worker/wrangler.toml. IPinfo Lite is the primary country and ASN source. IP-API Pro is the optional second provider and is skipped when no key is configured. Provider names alone do not activate a hidden URL: an endpoint must be configured for each provider.
The default Worker cache lifetime is one day (CACHE_TTL_SECONDS=86400). Cloudflare's Cache API is the durable edge tier. A bounded memory cache of up to 5,000 entries helps while an isolate remains warm. Cloudflare KV is supported through an optional GEO_CACHE binding but is deliberately unbound in the public deployment.
Run Wrangler commands from worker/:
npm ci
npm test
npx wrangler deploy
npx wrangler secret put IPINFO_TOKENOnly add the secrets you actually use:
| Secret | Needed when |
|---|---|
IPINFO_TOKEN |
IPinfo Lite is enabled |
IP_API_KEY |
IP-API Pro is enabled |
VPS_GATEWAY_URL |
The Worker should use the independent fallback server |
VPS_GATEWAY_TOKEN |
Must match a token accepted by that fallback server |
GATEWAY_TOKENS |
Optional private Worker access; leave unset for the public Worker |
STATUS_TOKENS |
The private /status dashboard; leave unset to keep the page hidden |
The VPS_ names are retained for backward compatibility. They refer to the fallback server, not a requirement to use a particular hosting product. Never commit token values to this repository or place them in wrangler.toml.
GET /status on the Worker is an operator-only view of every lookup path. It stays invisible (404) until a STATUS_TOKENS secret exists; after that it answers only to that token and live-probes each provider, the fallback server health route, and all three cache tiers on every load.
npx wrangler secret put STATUS_TOKENS
# then open in a browser:
# https://<your-worker>/status?key=<token>
# or send: Authorization: Bearer <token>Append &format=json for machine-readable output. The page is marked noindex, never cached, sends no CORS headers, and returns 404 to any request without the token. Each view performs real provider probes, so treat it as an on-demand check rather than something to poll aggressively.
The public Worker is https://geoprotect.demonzdevelopment-e64.workers.dev. The public wiki is https://georestrict-docs.pages.dev. Publish wiki changes from the repository root with:
npx wrangler pages deploy docs --project-name georestrict-docsThe fallback server has no public plugin-facing URL. Keep it behind the Worker.
The service in vps-gateway/ is the last route, not the public entry point. The Worker calls it only after its external providers fail. It uses local DB-IP Country Lite and ASN Lite MMDB files, so a player lookup makes no further geolocation API request and needs no SQL database.
Install it on a Linux server with Node.js 18 or newer:
cd vps-gateway
cp .env.example .env
npm ci --omit=dev
npm run update-db
npm test
npm startKeep HOST=127.0.0.1 and PORT=8787. Put Caddy or nginx in front of it, publish HTTPS on TCP port 443, and keep TCP port 8787 closed to the internet. No UDP port is needed. Set a long random GATEWAY_TOKENS value in the server .env, then store the matching URL and token in the Worker secrets VPS_GATEWAY_URL and VPS_GATEWAY_TOKEN.
The supplied systemd units are in vps-gateway/deploy/. The service expects /opt/georestrict-vps/current to point to a real release directory, such as /opt/georestrict-vps/releases/20260714214358, and verifies current/src/server.js before starting. The database timer refreshes and validates the local DB-IP files monthly. The historical georestrict-vps filenames and /opt/georestrict-vps path remain for compatibility; user-facing documentation calls this component the fallback server.
The v2.1.0 release passed unit, integration, Worker, and fallback server verification across supported platform runtimes:
| Runtime | Version tested | Result |
|---|---|---|
| Paper | 1.21.11 build 132 | Passed |
| Purpur | 1.21.11 build 2568 | Passed |
| Folia | 1.21.11 build 14 | Passed |
| Spigot | 1.21.11, BuildTools 201 | Passed |
| CraftBukkit | 1.21.11, BuildTools 201 | Passed; direct connections |
| BungeeCord | 26.1-R0.1 snapshot build 2102 | Passed |
| Waterfall | 1.21 build 615 | Passed |
| Velocity | 4.0.0 build 6 | Passed on Java 25 |
The live clients use offline accounts on isolated loopback servers. Public addresses are supplied through trusted test forwarding metadata. These results do not certify every Minecraft version, online account authentication or a deployed Cloudflare environment.
GeoRestrict targets Java 17 bytecode, but the platform can require a newer runtime. Follow the runtime requirement of the server or proxy version you install.
| Command | What it does |
|---|---|
/georestrict check <ip|player> |
Shows the lookup result used by the rule engine |
/georestrict cachestats |
Reports local cache size and hit information |
/georestrict purgecache |
Clears cached lookups |
/georestrict reload |
Validates and reloads config.yml |
/georestrict |
Shows plugin information and version |
georestrict.admin grants admin commands. georestrict.bypass skips enforcement on Bukkit-compatible backend servers, where live player permissions are available at login.
Other plugins can query GeoRestrict programmatically via GeoRestrictAPI across Paper/Spigot, BungeeCord, and Velocity:
import zip.linuxaddict.georestrict.api.GeoRestrictAPI;
// Check if GeoRestrict API is ready
if (GeoRestrictAPI.isAvailable()) {
// Asynchronously check an IP address
GeoRestrictAPI.checkIp("1.1.1.1", "PlayerName").thenAccept(result -> {
if (!result.allowed) {
System.out.println("Blocked: " + result.reason);
System.out.println("Country: " + result.info.countryCode);
}
});
// Quick boolean check
GeoRestrictAPI.isAllowed("8.8.8.8", "PlayerName").thenAccept(allowed -> {
System.out.println("Allowed: " + allowed);
});
// Retrieve cached geo data
GeoResponse cached = GeoRestrictAPI.getCachedResponse("8.8.8.8");
// Geo-Routing without blocking: Get ISO country code
GeoRestrictAPI.getCountryCode("1.1.1.1").thenAccept(country -> {
System.out.println("Country: " + country); // e.g. "US", "DE"
});
// Raw Geo Lookup (country, ASN, ISP, VPN flags)
GeoRestrictAPI.lookup("1.1.1.1").thenAccept(geo -> {
if (geo != null) {
System.out.println("ISP: " + geo.asName + ", VPN: " + geo.isVpn);
}
});
// More getters: full country name, AS organization name and provider signal flags
GeoRestrictAPI.getCountryName("1.1.1.1").thenAccept(name -> System.out.println(name));
GeoRestrictAPI.getAsnName("8.8.8.8").thenAccept(name -> System.out.println(name));
GeoRestrictAPI.isHosting("8.8.8.8").thenAccept(hosting -> System.out.println(hosting));
GeoRestrictAPI.isProxy("8.8.8.8").thenAccept(proxy -> System.out.println(proxy));
GeoRestrictAPI.isMobile("8.8.8.8").thenAccept(mobile -> System.out.println(mobile));
// Batch lookups in parallel; insertion order preserved, failed IPs resolve to null
GeoRestrictAPI.lookupAll(java.util.List.of("1.1.1.1", "8.8.8.8")).thenAccept(map -> {
map.forEach((ip, geo) -> System.out.println(ip + " -> " + geo.countryCode));
});
// Offline cache helpers: never touch the network
String cachedCode = GeoRestrictAPI.getCachedCountryCode("1.1.1.1");
int entries = GeoRestrictAPI.getCacheSize();
GeoRestrictAPI.purgeCache();
}On Bukkit/Paper, GeoRestrictService is also registered with standard ServicesManager:
GeoRestrictService service = Bukkit.getServicesManager().load(GeoRestrictService.class);mvn -B clean verify
cd worker && npm test
node --check worker/src/index.js
node --check vps-gateway/src/server.jsThe shaded patch jar is written to target/georestrict-2.1.0.jar.
The integration and real platform startup harnesses are documented in test/README.md.
GeoRestrict was created by linuxaddict and is now maintained by the Demonz Development open-source community. Listening to users comes first: bring bugs, reports and honest feedback to our Discord community or the issue tracker.
Before running GeoRestrict for a public community, read the Privacy Policy and Terms and Conditions.
Released under GPL-3.0.
