Self-hosting the Resource Pack
ResourcePackManager ships its own small HTTP server. When autoHost: true (the default) and preferSelfHost: true (the default), the plugin tries to host the merged pack from the same JVM as the Minecraft server itself — no external file host, no separate web server, no manual URL paste.
This page covers how that self-host path works, what the sanity checks do, how the port and hostname are chosen, and what to configure when the defaults don't fit your setup.
If instead you want to host the zip through your own existing web server outside of RSPM, see the Troubleshooting page section on autoHost: false.
Delivery Decision Tree
When a player joins, RSPM picks a delivery URL using this precedence:
selfHostForce: true— straight to self-host, no probes, no remote upload. Mainly for testing the self-host path. Bypasses every other flag.preferSelfHost: trueANDselfHostEnabled: trueAND not in network mode — try self-host with three sanity checks (see below). If all pass, commit to self-host. If any fails, fall back to the remote path.- Otherwise — upload the pack to
https://magmaguy.com/rsp/and announce that URL. If the upload fails or SHA1 check reportsSESSION_NOT_FOUND, fall back to self-host (assumingselfHostEnabled: true).
Once a URL is in hand, RSPM uses Minecraft's multi-pack API on 1.20.3+ (so it coexists with other server-sent packs) and the older single-pack method on older versions.
The Three Sanity Checks
When preferSelfHost: true, RSPM runs these checks in order before committing to self-host:
Layer 1 — Heuristic check on the resolved external host
If the resolved host (see "External Host Detection" below) is RFC1918 (10.*, 172.16-31.*, 192.168.*), loopback (127.*), link-local (169.254.*), or unspecified (0.0.0.0), self-host can't possibly work for internet clients. Skip immediately and use remote hosting.
This catches the very common "ipify lookup failed, fell back to LAN IP" failure mode.
Layer 2 — Localhost self-probe
Open a HEAD request to http://127.0.0.1:<port>/rspm.zip, verify HTTP 200 and non-empty body. Catches:
- port-bind collisions (something else is on the chosen port)
- missing pack file (the route is registered but the zip isn't on disk yet)
- route-registration bugs
Times out aggressively (3 s) so a slow probe can't drag out boot.
Layer 3 — External reachability probe
POST the announced URL to POST /rsp/probe on the magmaguy.com hoster. The hoster fetches the URL from a public vantage point (with SSRF guards and a tight timeout) and reports back whether it's reachable.
Catches the most common production failure mode: the server has a public IP but the HTTP port isn't forwarded at the router or firewall. Layer 2 passes (the server responds on 127.0.0.1), but no real client could ever download the pack.
Decision policy on probe outcomes:
- reachable=true → external clients can reach our URL. Commit to self-host.
- reachable=false → external clients can't. Tear down self-host and use remote hosting (which is universally reachable from magmaguy.com).
- probe communication itself fails (IOException) → couldn't verify either way. Default to keeping self-host: refusing on inability-to-probe would be paradoxical because the remote path also needs magmaguy.com.
What the checks still don't detect
The NAT-hairpin edge case where the port is open to the public internet (Layer 3 passes) but the operator's own router doesn't loop traffic back from inside the LAN. External clients work, but the operator testing from the same machine fails.
Workaround for now: when testing from the host machine with a hairpin-broken router, set preferSelfHost: false OR selfHostExternalHost: 127.0.0.1.
Port Resolution
Two settings interact:
selfHostPort— explicit port (any positive integer) or-1(default) for auto-derivation.networkHttpOffset-v2— only consulted whenselfHostPort = -1. Added to the Minecraft server port. Default1.
The default is selfHostPort: -1 + networkHttpOffset-v2: 1, so:
- MC port 25565 → HTTP port 25566
- MC port 25584 → HTTP port 25585
This auto-staggers HTTP ports across backends on a single-host network without any admin configuration — each backend already has a unique MC port so each gets a unique HTTP port.
Why offset 1?
Most shared / managed Minecraft hosting (Pterodactyl-based panels, etc.) allocates a narrow port range per container (often only 4–10 ports). Larger offsets land outside the range and the host firewall silently blocks the HTTP port. Offset 1 fits even tight allocations.
Self-hosted admins with full port control can bump this to any value, but if you run a proxy network you must also bump the proxy's network-http-offset-v2 to match.
Watch out: RCON collision
If your host enables RCON by default on MC port + 1, choose offset 2 or 3 to avoid a port collision. Check server.properties for rcon.port=.
Versioned config key
The setting in config.yml is literally named networkHttpOffset-v2. The v1 key was networkHttpOffset with default 100 — that default broke on shared / managed hosting where each game container only gets ~4–10 consecutive ports, MC + 100 fell outside the range, the HTTP server bound internally but the host firewall dropped external traffic, and the proxy got a silent CONNECT_FAILED forever. v2 ships with default 1 so MC + 1 stays well inside even the narrowest container allocations.
If you upgrade from v1, the dead v1 key sits in your config as a harmless artifact until you clean it up — RSPM intentionally does not read it.
External Host Detection
selfHostExternalHost controls what hostname clients see in the URL. Leave empty (default) to auto-detect in this priority order:
- api.ipify.org / checkip.amazonaws.com — returns the public IPv4 of this host. Cached once per
/rspm reloadso the IP services aren't hammered. Bukkit.getIp()— the server's bind address, when non-empty and not0.0.0.0. Usually a LAN address.InetAddress.getLocalHost()— best-effort.localhost— last-resort fallback. Clients outside the box won't reach this.
If auto-detection lands on a non-routable address and preferSelfHost: true, the Layer 1 heuristic check fails and the plugin switches to remote hosting.
For the most reliable self-host setup, set selfHostExternalHost explicitly to your public hostname (e.g. play.example.com). This skips the ipify/AWS detection entirely and the probe runs against the explicit value.
Configuration Reference
# Whether the built-in HTTP server may be used as a delivery path at all.
# When false, self-host is never attempted regardless of the other flags.
selfHostEnabled: true
# Port for the self-host HTTP server.
# -1 (default) = auto-derive: HTTP port = Minecraft server port + networkHttpOffset-v2.
# Set to any positive value to force an explicit port.
selfHostPort: -1
# Added to the Minecraft server port when selfHostPort = -1.
# Default 1 (MC 25565 -> HTTP 25566). Must match the proxy's network-http-offset-v2
# in proxy networks.
networkHttpOffset-v2: 1
# Public hostname or IP clients use to reach the self-host server.
# Leave empty for auto-detect (api.ipify.org / checkip.amazonaws.com).
selfHostExternalHost: ""
# Try self-host FIRST with three sanity checks, fall back to remote if any fail.
# When false, use the legacy order: remote upload first, self-host only on upload failure.
preferSelfHost: true
# Skip ALL other delivery paths and force self-hosting.
# Bypasses sanity checks AND remote upload. Mainly for testing.
selfHostForce: false
Backend HTTP Server Routes
The built-in HTTP server always serves the pack zip at:
http://<host>:<port>/rspm.zip
In network mode (RSPM is behind a Velocity / BungeeCord / Waterfall proxy) two additional routes are registered for the proxy plugin to pull:
http://<host>:<port>/bedrock.zip # the Bedrock-converted pack
http://<host>:<port>/mappings.json # the Geyser custom-mappings JSON
All three routes are file-backed: the route reads the file fresh on every request, so a re-mix that rewrites the same zip path is picked up automatically without restarting the HTTP server. The Bedrock routes support If-Modified-Since so the proxy poller pays roughly zero bandwidth when nothing has changed. All routes return 404 cleanly when the underlying file is absent (e.g. before the first mix completes).
Verifying Self-host is Active
/rspm status shows:
Active delivery: SELF-HOSTED— self-host is in useActive delivery: REMOTE (magmaguy.com)— remote auto-host is in useURL: ...— the actual URL clients will seeResolved external host— whatselfHostExternalHostresolved toPublic IP (auto-detected)— what ipify/AWS reported, if anythingselfHostPort— auto vs explicit, and the resolved value
If you expected self-host but see remote, the boot log explains which sanity check failed and why.
Common Mistakes
- Setting
selfHostPortto an arbitrary value and forgetting it on the proxy — in network mode the proxy usesmcPort + network-http-offset-v2to compute each backend's HTTP port. An explicitselfHostPortoverrides the auto-derivation but the proxy doesn't know about it, so it will still pollmcPort + offset. LeaveselfHostPort: -1in network mode unless you're prepared to coordinate. - Trusting the Layer 3 probe with a hairpin-broken router — the probe runs from magmaguy.com's vantage point, so it can't catch the case where external clients work but the operator's own LAN doesn't loop back. Test from a phone on cellular if you're unsure.
- Bumping
networkHttpOffset-v2past your hosting provider's port range — the symptom is a silent CONNECT_FAILED forever on the proxy side. Check thatmcPort + offsetis in your container's allocated port band before raising the offset.