Skip to main content

Proxy Networks (BungeeCord / Velocity)

ResourcePackManager runs on proxy networks. Install the same ResourcePackManager.jar on the proxy and on each backend that supplies resource packs. Other registered servers can remain without RSPM. On a backend it merges, hosts, delivers and converts packs. On Velocity or BungeeCord, the jar detects the proxy platform and handles network Bedrock delivery. The proxy owns the network key and sends it to each backend when a player first connects there.

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. Discovers which registered backends run RSPM, then polls those participants every 5 seconds for /bedrock.zip and /mappings.json. It prefers strong ETags with If-None-Match and retains If-Modified-Since compatibility for older backends. Unchanged files return 304. Each backend announces the exact HTTP port it bound, so the proxy normally uses that 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. When item mappings exist, 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.

Java pack delivery on networks happens per-backend through the regular setResourcePack / addResourcePack API. The proxy role adds network key provisioning and authenticated RSPM update propagation alongside Bedrock pack merging. A Java-only network does not need it for Java pack delivery.

Setup​

Floodgate​

Floodgate lets Bedrock players authenticate without a paid Java Edition account. Geyser can also use online authentication with a Java account; follow the Geyser setup guide for the authentication mode you use. RSPM can link its proxy and backends without Floodgate.

If Floodgate is installed when RSPM first boots on the proxy, RSPM derives its network key from plugins/floodgate/key.pem, preserving an existing network's identity. Later boots use RSPM's own network-key file while it contains a valid key. If that saved key is missing or invalid, RSPM tries the Floodgate seed again before generating a random key.

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 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.

If you already started the backends without putting RSPM on the proxy, you don't have to go hunting for the download: each proxied, unkeyed backend stages a byte-identical copy of its own jar at plugins/ResourcePackManager/proxy-extension/ResourcePackManager.jar and prints that exact path in the console. Copying that file installs the version currently running on that backend; update the other backends as well to keep the network aligned.

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

Proxy softwareUse this jar
VelocityResourcePackManager.jar
BungeeCordResourcePackManager.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.

Restart the proxy. That's it.

There is no required config to edit and no key to paste. The proxy establishes the network key itself at boot and provisions each backend with it automatically — see How the proxy and backends link below. The generated proxy config.yml contains only two optional settings, both described further down.

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.

Backend discovery​

The proxy checks registered servers for RSPM without making pack synchronization wait for servers that do not run it. Endpoint announcements and previously validated cached content can identify participants immediately. Otherwise, discovery checks the resolved HTTP endpoint in the background, with at most two checks at once and a five-second timeout. Unconfirmed servers are checked again after about a minute.

/rspm status lists the confirmed participant count and each remaining server's discovery state. RSPM not confirmed means discovery has not established that RSPM is present; it does not prove the plugin is absent. Check the backend address, HTTP port and reachability if a server should participate. Installing RSPM on another backend is discovered automatically.

Once a backend is confirmed, connection failures are reported as failed participant refreshes. Available committed content is retained, and direct and relay results remain visible in /rspm status. A zero empty-poll count means content is available, not that every backend connection is healthy.

Discovery does not grant update access. Network-key authentication and jar validation still apply to automatic plugin updates.

How the Proxy and Backends Link​

Everything that follows happens without configuration. It is documented here because when a network doesn't link, knowing the sequence is the whole diagnosis.

1. The proxy establishes the key. On boot it resolves exactly one value, in this order:

  1. its saved network-key file, in the proxy plugin's own data folder — the steady state on every boot after the first;
  2. a one-time seed derived from plugins/floodgate/key.pem on the proxy, if that file exists;
  3. a freshly minted random key.

The resolved key is saved to network-key and reused while valid. The console reports its source (Network key loaded ✓, adopted from Floodgate key.pem, or New network key generated). A malformed network-key file is replaced. If RSPM cannot save a new key, or cannot read an existing network-key file, the proxy role stops starting with Could not establish persistent RSPM network identity or Cannot read established RSPM network identity, rather than run on a key that could change at the next restart. Fix the folder permissions and restart the proxy.

2. The proxy pushes the key to each backend. As soon as a player connects to a backend, the proxy sends the key to it over the rspm:network plugin channel. The direction is deliberate: a backend physically cannot ask, because a proxy will not forward a plugin message on a channel the player's own client never registered.

3. The backend adopts and persists it. It saves the key to its data.yml and reports Network key received from the proxy; this backend is now linked. Grants are honoured only while the backend holds no key — an established backend logs a warning and keeps what it has rather than letting a message re-point it at another network.

On Velocity with modern forwarding, grants are signed with an HMAC over the proxy's forwarding.secret, and a backend configured for modern forwarding requires that signature. On BungeeCord and legacy forwarding there is no shared secret to sign with, so grants are unsigned and accepted as such.

A backend that reaches step 2 but never step 3 keeps its key source at not set. That is the exact state that triggers the proxy-extension/ staging and the Bedrock-specific warning banner.

Verifying It Works​

Proxy console​

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

[ResourcePackManager] Network key loaded ✓
[ResourcePackManager] Network key provisioning ready on rspm:network; backends are keyed as players connect.
[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: network key fingerprint, backend list, per-backend per-path fetch outcomes (200 / 304 / 404 / CONNECT_FAILED) for /bedrock.zip, /mappings.json and /rspm-update.jar, the endpoint announcements it has received, the relay entries it can see, the consecutive-empty-poll counter, whether the merged Bedrock pack and merged mappings are on disk (with their sizes) plus the current merged pack's SHA-1 prefix, the network-http-offset fallback, the detected Geyser plugin folder and deployed mappings file, and Floodgate / Geyser presence on the proxy. Velocity accepts resourcepackmanager.command.status or resourcepackmanager.*; BungeeCord registers the exact resourcepackmanager.command.status node, which permission plugins may satisfy through wildcard expansion. Console can run it. The output reveals no secrets (the network key appears only as a short one-way hash fingerprint, and no auth tokens are printed), so liberal grants are safe.

/rspm status on a backend​

The Deploy mode line should read network-backend. Network key fingerprint should show a short hash — it must match the fingerprint the proxy prints. Network key source tells you how this backend got it:

Source lineMeaning
provided by the proxyThe normal linked state. The proxy granted the key on a player connect.
saved on this serverAlready keyed on a previous boot (or a standalone server that minted its own).
adopted from Floodgate's keySeeded once from this backend's local plugins/floodgate/key.pem.
not set — the proxy sends one when a player next connects hereNot linked. Either no player has connected since the proxy came up, or the proxy is missing RSPM / is an unsupported proxy.

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 IP-forwarding switch.
  3. paper-global.yml: proxies.velocity.enabled: true — modern Velocity forwarding.

The port fallback setting 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). The default is 1; the corresponding port must still be reachable between the proxy and backend, or the relay path must be available. 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 config.yml​

The proxy plugin writes a minimal default config on first boot. The folder differs by platform, because each proxy derives it from its own plugin identifier:

Proxy softwareConfig path
Velocityplugins/resourcepackmanager/config.yml
BungeeCordplugins/ResourcePackManager/config.yml

It contains exactly two settings, both optional:

# 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

# Installs and updates this universal ResourcePackManager.jar in the proxy's
# Geyser extensions folder, which is what makes custom Bedrock ENTITIES render.
# Default true. Setting it to false stops future installation and update
# staging; it does not delete an extension jar that is already installed —
# remove that by hand while Geyser is stopped.
geyser-extension-auto-install: true

The backend equivalents are networkHttpOffset-v2 and geyserExtensionAutoInstall in the backend config.yml. The two sides are separate settings — turning the extension install off on the proxy does not turn it off on the backends, and vice versa.

Alongside config.yml, the proxy writes network-key. Keep this generated state intact and do not copy it between different networks. Deleting it repeats key resolution on the next boot; an available Floodgate seed can reproduce the same key. If the key changes, provisioned backends keep their old key and reject the replacement. Two proxies serving the same backends must share one key: copy the file between those proxies.

There is no proxy-side force-resource-pack setting. Forcing pack acceptance is a backend-side decision (forceResourcePack in the backend config.yml) because the proxy plugin only handles Bedrock pack delivery — it never sends Java packs. If an older proxy config.yml still carries a force-resource-pack line, it is simply ignored and can be deleted.

There is intentionally no network-key config entry — a pasteable key was retired pre-release because typos silently broke the proxy↔backend link with no error anywhere. The key is established by the proxy and provisioned to backends automatically, as described in How the proxy and backends link.

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​

Java hosting preferences also apply to proxy backends. If a Java self-host probe fails, RSPM can switch Java delivery to remote hosting while keeping the backend HTTP server running for /bedrock.zip and /mappings.json.

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 network 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​

A backend never gets a network key​

Symptom: /rspm status on the backend shows Network key source: not set — the proxy sends one when a player next connects here, and the backend console repeats:

[ResourcePackManager] This backend is behind a proxy but has no network key yet, so it is not linked to the proxy.
[ResourcePackManager] The proxy sends one automatically the first time a player connects to this server.

Work through these in order:

  1. Has anyone actually connected to that backend since the proxy started? The grant rides on a player connection. A lobby nobody has visited yet is legitimately unkeyed.
  2. Is RSPM on the proxy at all? The backend stages a copy of its own jar at plugins/ResourcePackManager/proxy-extension/ResourcePackManager.jar for exactly this case. Copy it into the proxy's plugins/ folder and restart the proxy.
  3. Velocity modern forwarding: does the backend's forwarding secret match the proxy's? A backend configured for modern forwarding rejects an unsigned or wrongly-signed grant on purpose — that check is the only thing preventing an unauthorized proxy from keying your backends. The backend log names which of the two cases it hit. Fix the secret on both sides and the next player connect retries automatically; no restart of either side is needed to re-arm.
  4. Is the proxy an unsupported proxy? Only Velocity and BungeeCord run the proxy role. Waterfall is not supported.

Note that Floodgate's key.pem is no longer part of this. A missing plugins/floodgate/key.pem on the proxy is a fully supported state — the proxy mints its own key instead. Floodgate authentication is separate from RSPM network linking.

Two proxies front the same backends​

Both proxies must present the same network key, or each backend links to whichever one reached it first and rejects the other (logging A proxy offered a network key that differs from the one this backend already uses). Copy the network-key file from the primary proxy's RSPM data folder to the other proxy's, and restart it.

"No merged pack content" warning after ~20 seconds​

After four consecutive empty poll cycles with confirmed RSPM participants, the proxy logs a warning listing those participants, the URLs it tried and their outcomes. A fresh proxy still discovering its first participant does not count unrelated registered servers as failed pack backends. 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 convertible item mappings or entity bundles 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 proxy has logged its first Merged Bedrock pack published line. RSPM pre-deploys the previous run's mappings on every proxy boot, so subsequent boots can load the saved mappings before Geyser scans. Adding or removing custom items later also changes the boot-time mapping set and requires a restart; new custom-entity identifiers or property types require the same treatment.

"Duplicate bedrock_identifier" warnings on proxy boot​

Different Bedrock identifiers can still share the same Java item/model/predicate matcher. Renaming only a resolved source model does not make two such items distinct. Give distinct items distinct selectors, for example separate custom-model-data values. The current merger keeps a modern definition over a legacy entry for the same matcher; between equal types, the later entry wins. Deduplication preserves one item, not both.

Bedrock player connected but doesn't see the pack​

The proxy sends a chat banner to online staff with resourcepackmanager.* 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​

It's the same jar on every backend and on the proxy, so keep all components on the same version.

The manual route is: bump the backend jar, re-copy that same ResourcePackManager.jar to the proxy's plugins/ folder, restart the proxy.

RSPM can also do the proxy half itself. Each backend offers its universal plugin jar to the proxy over an authenticated route (/rspm-update.jar, protected by a token derived from the shared network key — the key itself is never transmitted). The proxy validates that the offered file is a complete universal RSPM jar with matching platform descriptor versions and expected entry points, refuses downgrades, and verifies the staged bytes against the advertised size and SHA-256 before replacing anything. It stages the same jar for the proxy plugin and, when proxy-hosted Geyser is detected, its bundled Geyser extension; it applies those files on proxy shutdown and keeps the previous files as rollback copies.

This is an authenticated network trust boundary, not an independent provenance check against Nightbreak: any backend that has been provisioned with the shared network key is trusted to offer a structurally valid same-or-newer RSPM jar. Protect that key and treat every backend sharing it as trusted infrastructure.

So in practice, updating the backends and then restarting the proxy is usually enough — the proxy will have staged the matching jar for itself. A rejected update logs Rejected backend-offered ResourcePackManager update: ... with the reason.

Executable-update authentication failures​

HTTP 401 on /rspm-update.jar pauses only automatic jar propagation from that backend. Direct Bedrock pack and mappings polling continues. The update route retries with a backoff starting at one minute and capped at fifteen minutes. Compare the network-key fingerprints from /rspm status on both sides, then verify the reported URL reaches the intended backend and versions match.

If the fingerprints differ, /rspm status on the proxy gives the current procedure: confirm this is the intended proxy and backend, stop the affected backend, back up its plugins/ResourcePackManager/data.yml, remove only its networkKey entry, then start it and join through this proxy so it is provisioned again. Keep the proxy's network-key and any Floodgate keys. The 401 console warning on the proxy still recommends copying Floodgate key.pem; that advice is stale, because RSPM persists its own network identity and changing key.pem does not replace an established RSPM key. Retain both status outputs for support.

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 rotation of the network key. A backend adopts a key only while unkeyed and rejects replacement messages. Clearing the proxy's network-key and every backend's networkKey in data.yml while all components are stopped allows provisioning again. This does not guarantee rotation: the proxy can derive the same key from Floodgate's key.pem again.