A Velocity-style proxy for Minecraft: Bedrock with Endstone/BDS backends. Players stay connected to the proxy while switching between backend servers; the proxy owns authentication and forges offline logins to the backends.
By analogy: an Endstone server is Paper, and Endlink is Velocity.
java -jar Endlink.jar [config.properties]
On first start it writes a fully documented config.properties and creates a plugins/ folder.
Proven in real use: a Minecraft 1.26.40 client against 1.26.40 backends. Players join, play, and switch between backends keeping their session, identity and permissions. That has run a live server at around ten concurrent players for a week.
New in v0.2.0, verified but not yet at scale: the cross-backend item, entity and block registries, resource packs loaded from unpacked folders, and backend packs cached and served by the proxy. Custom items, entities and blocks have been confirmed rendering correctly across a backend switch, and backend packs confirmed downloading and serving — by hand, with a handful of players, not yet under a full server. If something in this release misbehaves, this is where to look first.
Deliberately unfinished: the older-version translation chain (see Versions). Run the current Minecraft release on both ends.
- One connection, many servers. Players switch backends without reconnecting, keeping their session, identity and permissions.
- The proxy owns authentication. Backends run in offline mode behind it and are secured by EndlinkGuard, which rejects any join that did not come through the proxy.
- A version-translation layer for the window where a backend lags a Minecraft release, through a registry of adjacent-version translators that chain across longer gaps. Read the note on versions below before relying on it.
- Failover and forced hosts. A dead backend moves players rather than dropping them; a hostname can route to a specific backend.
- Addon content that survives the switch. Bedrock reads a client's resource packs, item registry,
entity list and block definitions once, at level init, and a seamless switch does not re-run it —
so without help, a backend's custom items draw arbitrary vanilla textures and its custom entities
and blocks are invisible to anyone who arrived from somewhere else. Endlink serves packs from
resourcePacks.dirand caches each backend's own packs as it learns them, and gives every client the combined registries of all backends, translating each backend's network ids to match. SeeresourcePacks.cacheBackendPacksandcrossBackendPalettein the config. - Rate limiting and abuse controls on the public listener: per-address session caps, connection attempt windows, RakNet packet limits and optional connection cookies.
- An addon API. Anything in
plugins/is discovered at startup and can extend the proxy. With an emptyplugins/folder, Endlink is exactly a Bedrock proxy and nothing else. - Minecraft: Java Edition players — in beta, through the optional ViaEndlink addon. Java clients join, chat and walk around; they cannot open a chest yet. Read its status notice before relying on it. Endlink itself does not need it and knows nothing about Java.
- Minecraft: Java servers as backends, through a Geyser instance in front of one. Bedrock players can be sent to a Java server alongside the Bedrock ones, keeping their identity and their player data. Moving across that boundary reconnects them to the proxy rather than handing them over, for a reason that cannot be worked around; see Minecraft: Java backends.
Keep clients and backends on the current Minecraft release. That is the configuration Endlink is built for, tested in, and the only one that is expected to work properly.
The translator chain that lets an older client onto a newer backend, or the reverse, exists because backends lag a release by days or weeks after an update. It is useful for exactly that gap. Beyond it, the older-version chain is unstable and incomplete, and it is not going to be finished.
That is a deliberate decision rather than a backlog item. Bedrock updates itself, there is no supported way for an ordinary player to stay on an old version, so a version-locked player is not a real audience to build for. Effort spent there buys nothing that keeping current does not.
Practically: after a Minecraft update, update the backends promptly. backend.protocol=auto reads
the version from each backend rather than trusting a pinned value, so the proxy follows them without
a config change — the pinned-value route is how version skew gets introduced by accident.
Everything lives in one config.properties, generated with comments explaining each setting — the
file the jar writes is byte-for-byte the documented template, so the copy on a server is its own
documentation. config.example.properties here is that template.
The settings worth knowing before a first run:
| Setting | Why |
|---|---|
backends / backend.<name>.host |
The servers players can be sent to |
backendVerification.sharedSecret |
Must match EndlinkGuard's shared_secret on every backend |
permissions.admins |
XUIDs allowed to run /send, /alert, /glist, /perm |
backend.protocol |
Leave auto — it reads the version from the backend rather than trusting a pinned value |
publicAddress |
Only for networks with a Java backend: the address players are sent back to when a move needs a reconnect. Empty uses the address each player connected with, which is usually right |
resourcePacks.dir |
Packs every client gets at login, as .mcpack files or unpacked folders |
resourcePacks.cacheBackendPacks |
Learns each backend's packs into cache/packs and serves them itself, so packs work after a switch without copying them into the directory above. The first player to switch to an unlearned backend waits for one download |
crossBackendPalette |
Leave on if any backend has custom items or entities — it is what keeps them rendering after a switch. The proxy learns each backend's registry on the first visit and caches it in cache/backend-palettes.nbt; after an addon change, the first player there sees the old registry until they rejoin |
Use Endstone as the backend server, with EndlinkGuard installed on it. That is the combination Endlink is built and tested against.
Endstone is a plugin-capable server built on Bedrock Dedicated Server, which is what makes the backend side of this possible: EndlinkGuard needs to run code during the join to check it.
EndlinkGuard is not optional in any real deployment. Endlink puts backends into offline mode so it can own authentication — that is what lets one session move between servers — and a backend in offline mode with nothing guarding it is open to anyone who learns its address. EndlinkGuard verifies every join against the proxy over an HMAC-signed handshake and rejects the rest.
Set backendVerification.sharedSecret here and shared_secret in EndlinkGuard's config.toml to
the same value. Mismatch fails closed: proxied joins are rejected and both sides say so in the log.
A backend can also be a Java server, reached through a Geyser instance
that the proxy treats as an ordinary Bedrock backend. Bedrock players join Endlink as usual and can
be sent to it alongside the Bedrock ones. Install
EndlinkGuard's geyser/ build into that Geyser's
extensions/ folder — it does the same job there as the Endstone build does on a Bedrock backend,
and additionally carries the player's real IP through to the Java server.
Nothing in Endlink's config needs setting for this. There is one behaviour to know about:
Moving to or from a Java backend reconnects the player instead of handing them over. A Bedrock client reads its block-id scheme from the StartGame it logs in with and cannot be told otherwise while it is playing. Bedrock servers hash block ids from the block state; Geyser numbers them by palette position. A seamless handoff across that boundary delivers chunks the client cannot decode — the player would stand in an empty or scrambled world with nothing in any log to say why.
So Endlink learns each backend's scheme from its first StartGame, remembers it, and moves players across that boundary by transferring them back to its own address and putting them where they asked. The player never leaves the proxy: same listener, same identity check, same permissions, and backends stay unreachable from outside. They see a loading screen. Switches between backends of the same kind are seamless as before.
That transfer needs an address to send the client back to. By default the proxy uses whatever address
each player connected with, which is correct per player and needs no configuration; set
publicAddress when that is not good enough.
Needs a JDK 21+ in JAVA_HOME. From this directory:
gradle build
dist/Endlink.jar is the deployable artifact. gradle test runs the suite. Nothing else is needed —
the Bedrock codecs and the RakNet transport are vendored under protocol/ and network/, so a fresh
clone builds on its own.
On Windows, clone with long paths enabled. The vendored codecs nest deeply enough to exceed the
260-character MAX_PATH limit, and git fails partway through with Filename too long:
git -c core.longpaths=true clone https://github.com/luibara2/endlink.git
Or enable it once for good: git config --global core.longpaths true.
| EndlinkGuard | The backend plugin. Verifies proxy joins and rejects direct ones — install it on every backend |
| ViaEndlink | Beta. Optional addon for Minecraft: Java Edition players joining Bedrock backends. Joining, chat and terrain work; containers do not — read its status notice first |
| Geyser | Not an addon: run one in front of a Java server to offer it as a backend. See Minecraft: Java backends |
| Endstone | The recommended backend server: plugin-capable Bedrock Dedicated Server |
Endlink is licensed under the Apache License 2.0 — see LICENSE. Use it, fork it, ship it,
including commercially; keep the notice and state what you changed.
It builds on Apache-2.0 work and keeps their notices:
| CloudburstMC Protocol | Apache 2.0 — the Bedrock codecs, via a fork carrying additional hand-written ones |
| Netty, jose4j | Apache 2.0 |
| Endstone | Apache 2.0 — the recommended backend server |
No GPL code is used here, deliberately. Anything built on GPL-3.0 libraries lives in a separate addon rather than in this repository, so that Endlink itself can stay permissively licensed. Bundling or linking such code here would place Endlink under GPL-3.0 too.