Skip to main content

Proxy Networks (BungeeCord / Waterfall / Velocity)

ResourcePackManager runs on proxy networks. There is one jarResourcePackManager.jar — and you install the same jar on every backend and on the proxy. On a backend it runs in backend role (merge, host, push, convert); on a Velocity or BungeeCord/Waterfall proxy the same jar auto-detects the platform loader and runs in proxy role (network-side Bedrock delivery). The two roles find each other automatically via the shared plugins/floodgate/key.pem file — no separate proxy jar, no proxy-extension to extract, no key to paste.

Throughout this page "the proxy plugin" is shorthand for that same ResourcePackManager.jar running in proxy role.

What the Proxy Plugin Does

The proxy plugin's job is Bedrock pack merging and delivery. On a network with several backends, each backend produces its own Bedrock pack (the converted version of its merged Java pack). The proxy plugin:

  1. Polls each backend's small HTTP server every 5 seconds for /bedrock.zip and /mappings.json, using If-Modified-Since so unchanged files cost roughly zero bandwidth. Each backend announces the exact HTTP port it bound, so the proxy normally hits the right port automatically (see Backend HTTP port resolution).
  2. Waits for the inbox state to stabilize — it merges as soon as two adjacent polls observe the same set of file hashes.
  3. Merges every backend's Bedrock pack into a single network-wide pack.
  4. Copies the merged Geyser custom-mappings file into plugins/Geyser-*/custom_mappings/ on the proxy.
  5. Serves the merged pack to Bedrock clients on connect via Geyser's SessionLoadResourcePacksEvent.

The proxy plugin is only useful for Bedrock players. Java pack delivery on networks still happens per-backend through the regular setResourcePack / addResourcePack API — Java clients see whichever backend's pack the backend they're on chose to send. If your network is Java-only, you don't need the proxy plugin.

Setup — Two Steps

Step 1: Install Floodgate on the proxy and on every backend

Floodgate is required for Bedrock players to authenticate on the proxy. RSPM piggybacks on Floodgate's plugins/floodgate/key.pem to derive the network identity — that file must be byte-for-byte identical on the proxy AND on every backend. Floodgate already requires this for Bedrock auth, so if Bedrock players currently work across your network, this is already done.

If you don't have Floodgate yet, install it on every component, then copy one canonical key.pem to every other component before continuing.

Step 2: Copy ResourcePackManager.jar to the proxy

There is only one jar. The same ResourcePackManager.jar you install on Bukkit/Paper backends is also the proxy plugin — it ships the Velocity and BungeeCord/Waterfall implementations together in one shaded jar and auto-detects which platform it's running on. There is no separate per-platform proxy jar to extract.

Copy the same ResourcePackManager.jar into your proxy's plugins/ folder:

Proxy softwareUse this jar
VelocityResourcePackManager.jar
BungeeCordResourcePackManager.jar
Waterfall (Bungee fork)ResourcePackManager.jar

You can confirm this from a backend at any time by running /rspm status; the Proxy deployment section prints Network proxy jar: ResourcePackManager.jar and Use the same jar on Bukkit/Paper, Velocity, and BungeeCord/Waterfall.

Restart the proxy. That's it.

There is intentionally no config to edit and no key to paste. The proxy plugin reads plugins/floodgate/key.pem on the proxy at boot and derives the network identity from it. Since Floodgate already requires that file to be the same on every backend and on the proxy, the derived key matches every backend's automatically.

The proxy's first poll fires ~2 seconds after boot, then every 5 seconds. Combined with the single-cycle stability gate, the first merge is published roughly 7 seconds after the proxy can see at least one backend that's already producing content.

Verifying It Works

Proxy console

Within ~10 seconds of restart (assuming backends are running) you should see:

[ResourcePackManager] Network-key auto-derived from Floodgate key.pem ✓
[ResourcePackManager] NetworkSync starting (poll interval 5000 ms, network-http-offset 1 - endpoint announcements preferred, fallback HTTP port = mcPort + offset)
[ResourcePackManager] NetworkSync: inbox stabilized — merging 2 Bedrock zip(s) and 2 mappings file(s) across 2 backend(s).
[ResourcePackManager] Merged Bedrock pack published at .../merged/Bedrock.zip (sha1=...).
[ResourcePackManager] ✔ Network resource pack is now ready (... KB, sha1 ABCD1234)

/rspm status on the proxy

Prints a snapshot: backend list, per-backend per-path fetch outcomes (200 / 304 / 404 / CONNECT_FAILED), consecutive-empty-poll counter, current merged pack path + size, network-http-offset, and Floodgate / Geyser presence on the proxy. Requires resourcepackmanager.command.status or resourcepackmanager.*. Console always has both; grant one to players via your permission plugin if you want them to run it. The output reveals no secrets (network key is masked, no auth tokens), so liberal grants are safe.

/rspm status on a backend

The Deploy mode line should read network-backend. The Network key line should show a masked key (last 4 chars visible) — match it against the proxy's masked key to confirm both sides agree.

Bedrock client

Connect via Bedrock. You should see the resource-pack download prompt before reaching the world. Custom items render with their intended models instead of plain armor stands.

Configuration Reference

Backend plugins/ResourcePackManager/config.yml

Network mode is auto-detected — there's no networkMode: true flag to set. Detection signals (any one is sufficient):

  1. Floodgate present, Geyser-Spigot absent — strongest signal for the Bedrock-via-proxy case.
  2. spigot.yml: settings.bungeecord: true — legacy BungeeCord / Waterfall IP-forwarding switch.
  3. paper-global.yml: proxies.velocity.enabled: true — modern Velocity forwarding.

The only knob that matters specifically for network mode is networkHttpOffset-v2, which controls the fallback HTTP port the proxy will poll on each backend (the port the proxy guesses before a backend has announced the port it actually bound). Default 1 works on virtually all hosting. See Self-hosting for the full port-resolution story.

In normal operation each backend announces its real HTTP port to the proxy automatically (via the magmaguy.com endpoint registry), so the offset is only consulted as a startup/failure fallback. See Backend HTTP port resolution below.

Proxy plugins/ResourcePackManager/config.yml

The proxy plugin writes a minimal default config on first boot:

# Force clients to accept the pack (kick on decline). Default: false.
force-resource-pack: false

# FALLBACK offset added to each backend's Minecraft port to derive the HTTP
# port this proxy will hit for /bedrock.zip and /mappings.json BEFORE that
# backend has announced its real ResourcePackManager HTTP port. Default 1.
# In normal operation the backend announces the exact port it bound, so this
# value is only used at startup or if announcement fails — but if it IS used,
# it should match each backend's networkHttpOffset-v2.
network-http-offset-v2: 1

There is intentionally no network-key entry — that was retired pre-release because manual paste was a major source of misconfiguration (typos silently broke the proxy↔backend link). The key is auto-derived from plugins/floodgate/key.pem.

Backend HTTP Port Resolution

The proxy needs to know which HTTP port to poll on each backend for /bedrock.zip and /mappings.json. It resolves that port in this order:

  1. Backend-announced endpoint (preferred). Each backend uploads the exact HTTP port it bound to the magmaguy.com endpoint registry, keyed by the network key. On each poll the proxy refreshes this list and matches a backend's announced port to the server-list entry by Minecraft port (and host when available). This means an admin who sets an explicit selfHostPort on a backend, or whose backend lands on mcPort + 1 automatically, is handled identically — the proxy uses whatever the backend actually bound.
  2. mcPort + network-http-offset-v2 (fallback). Used only when no matching announcement is available (e.g. the first poll before any backend has announced, or the endpoint registry is briefly unreachable). This is why the two offsets should still agree if you rely on the fallback.

The proxy deliberately does not port-scan a backend as a fallback — that looks like abusive behavior to the host. When direct fetch can't work at all, the relay path (below) is the categorical answer.

/rspm status on the proxy shows, per backend, which port was chosen and whether it came from an announcement or the offset fallback.

Stability and Merge Cadence

The proxy waits for the inbox state to stabilize before merging: the first poll sets the baseline file-hash set, and the next poll that observes the same set triggers the merge (a one-cycle stability gate). With the ~2 s initial delay and 5 s poll interval that puts the first merge ~7 s after the proxy can see at least one backend producing content. Subsequent merges only re-zip when the SHA-1 set of inbox files changes, so a long quiet period costs roughly nothing.

Backends write their bedrock.zip to a temp file and atomically rename it, so the /bedrock.zip route always serves a complete zip — the proxy never has to defend against half-written reads, which is why the gate is a single cycle rather than two.

Direct Fetch vs Relay Fallback

The default path is direct fetch: the proxy makes an HTTP GET to http://<backend-host>:<mcPort + offset><path> for each backend.

If the proxy can't reach a backend's HTTP port directly (typical of shared / managed Minecraft hosting where MC ports are exposed but adjacent ports are firewalled), the backend pushes its bedrock.zip and mappings.json to a relay endpoint on magmaguy.com under the network's namespace (derived from the Floodgate key). The proxy lists and downloads from the relay when direct fetch fails hard.

Both paths feed into the same merge step, so the operator never has to choose — direct fetch is preferred (zero bandwidth cost to magmaguy.com), relay kicks in transparently when needed.

The relay has a 30-minute TTL on the server side. Backends push every 25 minutes to keep the entry alive; clean shutdown drops the entry immediately rather than waiting for TTL.

Troubleshooting Common Issues

"Floodgate key.pem missing" on proxy boot

The proxy plugin couldn't find plugins/floodgate/key.pem and idled. RSPM cannot link to any backend without it.

Fix: install Floodgate on the proxy, then make sure plugins/floodgate/key.pem on the proxy is byte-for-byte identical to the same file on every backend. Floodgate auto-generates different keys per install by default — copy one canonical key.pem from any backend to every other component, restart everything.

"No merged pack content" warning after ~20 seconds

After 4 consecutive empty poll cycles, the proxy logs a one-shot multi-line warning listing every backend it polled, the URL it tried, and the outcome. The warning explains the most common fixes:

  • CONNECT_FAILED on every backend — the proxy can't reach the backend HTTP port at all. Check that the address in velocity.toml / config.yml is one the proxy can actually reach (not a Docker-internal name that doesn't resolve from the proxy's network), and that the backend's HTTP port is open between proxy and backend. The warning prints the exact HTTP host:port it tried for each backend and whether that port came from the backend's announcement or the mcPort + networkHttpOffset-v2 fallback. If you have no way to open that port (managed hosting), see the relay fallback section above — the backend should be uploading to the relay automatically.
  • NOT_FOUND_404 on every backend — backends are up but not producing a Bedrock pack. Run /rspm status on each backend; the Bedrock Pack diagnostic block will tell you why (most often: no items definitions in the merged pack, or the first mix cycle hasn't completed yet).

The warning fires once per stuck period. An NetworkSync: recovered line is logged when at least one backend starts returning content again.

Bedrock players see no custom models on first proxy boot

Geyser registers its custom-item table at proxy startup only. If the proxy started before any backend produced a Bedrock pack, Geyser is running with an empty mappings table and stays that way for the rest of the session.

Fix: restart the proxy once after the backend has logged its first Merged Bedrock pack published line. RSPM pre-deploys the previous run's mappings on every proxy boot, so this only ever bites on a brand-new install — subsequent boots have something ready before Geyser scans.

"Duplicate bedrock_identifier" warnings on proxy boot

Two backends emitted the same Bedrock identifier for the same base item. Last-writer-wins; harmless if you only need one backend to provide that item. If both backends should host distinct custom items under the same base item, rename one of the source Java models so the auto-generated hashes differ.

Bedrock player connected but doesn't see the pack

The proxy fires a chat banner to all online Java players when a Bedrock session loads without a usable RSPM pack:

⚠ [RSPM] Bedrock player Alice connected before the resource pack was ready — they're seeing plain armor stands instead of custom models. Tell them to disconnect and reconnect; the pack will load on their next session. (Cause: ...)

The Bedrock player themselves also gets a modal popup, and the proxy console gets a banner. If you need to dig deeper, enable the debug stream (available on both Velocity and BungeeCord):

/rspm debug bedrock on

That turns on verbose [RSPM-BedrockDebug] log lines from GeyserBinder. Reproduce the issue, then turn it off:

/rspm debug bedrock off

The setting resets to off on proxy restart so it can't accidentally be left on.

Updating RSPM on a Network

After bumping the backend RSPM jar, also re-copy the same ResourcePackManager.jar to the proxy's plugins/ folder and restart the proxy. It's the same jar on every backend and on the proxy, so keep all components on the same version.

What's Not Yet Supported

  • Java packs on networks via the proxy plugin. Java clients receive packs from each backend directly through the regular API. There is no proxy-side Java-pack mixer.
  • Cross-backend Java pack merging. Each backend's Java pack is independent. If a player switches backends, they receive the new backend's pack.
  • Live reconfigure of the network key. The key is bound to Floodgate's key.pem. Changing it requires restarting the proxy AND every backend (which is what you'd do when rotating the Floodgate key anyway).