A Velocity-CTD plugin that automatically transfers legacy Lunar Client players to a fallback proxy when the proxy shuts down, using the Apollo Transfer module.
Velocity-CTD already evacuates players when a proxy shuts down, but only clients on 1.20.5+
understand the vanilla transfer packet. Everyone on an older protocol (< 1.20.5) is simply
disconnected instead of being moved.
ApolloPlayerTransfer fills that gap: for legacy players running Lunar Client, it uses Apollo to redirect them to the exact same fallback proxy Velocity-CTD would have chosen, so a proxy restart no longer drops your legacy players.
| Player | Handled by |
|---|---|
1.20.5+ (any client) |
Velocity-CTD native transfer packet |
< 1.20.5 on Lunar Client |
ApolloPlayerTransfer (this plugin) |
< 1.20.5 not on Lunar |
Velocity-CTD (disconnected, no transfer is possible) |
- The plugin listens for
ProxyPreShutdownEvent, which fires before Velocity-CTD disconnects its players. - It asks Velocity-CTD itself for the fallback proxy (
VelocityServer#getProxyAddressToUse()), so the destination is resolved with the same logic as the native transfer: yourdynamic-proxy-filter(FIRST_FOUND/MOST_EMPTY/LEAST_EMPTY/NONE), self-exclusion viaproxy-id, and Redis-based player counts. - For every connected player that is on a legacy protocol and running Lunar Client, it sends an Apollo transfer to that proxy.
The plugin has no configuration file of its own, everything is read from your existing
velocity.toml ([proxy-addresses], dynamic-proxy-filter, [redis] proxy-id).
- Velocity-CTD (the fallback selection relies on CTD's
[proxy-addresses]machinery, plain Velocity is not supported). - Apollo-Velocity installed on the proxy, with the Transfer module enabled.
- A multi-proxy setup (typically with Redis, required for the
MOST_EMPTY/LEAST_EMPTYfilters and cross-proxy player counts). - Java 21+.
- Drop both plugins into your proxy's
plugins/folder: - Make sure the Transfer module is enabled in Apollo's configuration.
- Configure
[proxy-addresses]anddynamic-proxy-filterin yourvelocity.toml(this is standard Velocity-CTD setup, the plugin just reads it). - Restart the proxy.
These two steps are not required for the transfer to happen, but they make it invisible to the player. Both are Velocity-CTD / DNS configuration, no plugin settings involved.
By default, Lunar Client shows a "you are being transferred" confirmation screen. You can auto-accept
transfers between your own domains by adding a mc_transfer_accept_from TXT record on your root
domain. Lunar queries the target's domain and its parents, so a single record on the apex covers all
subdomains:
example.com TXT mc_transfer_accept_from=*.example.com,example.com
The client checks the address it is currently connected to against this whitelist; if it matches, the transfer is accepted with no prompt. See the Apollo Transfer docs for details.
Tip: the plugin sends a bare hostname (the default port
25565is omitted) so Lunar performs its DNS lookup on a clean domain name rather than ahost:portstring.
When a transferred player reconnects to the fallback proxy faster than the old proxy can release its Redis session, Velocity-CTD rejects the login. Enable session displacement so the fallback proxy kicks the stale session instead of refusing the new one:
kick-existing-players = true
kick-existing-players-check-ip = truekick-existing-players-check-ip = true restricts displacement to reconnections from the same IP,
which is exactly the transfer scenario, safe, and it prevents UUID-spoofing abuse. Apply this on
every proxy in the network.
mvn clean packageThe jar is produced in target/. The build uses the Lunar Client Maven repository
(https://repo.lunarclient.dev) for the apollo-api dependency (provided scope).
- Graceful shutdowns only. Transfers happen on
ProxyPreShutdownEvent. A hard crash or power loss runs no code, so those players cannot be transferred by any plugin. MOST_EMPTY/LEAST_EMPTYrequire Redis. Without it, Velocity-CTD returns no fallback and no transfer occurs, this mirrors CTD's own behavior.- Legacy Lunar players only. Modern clients are already handled natively by Velocity-CTD; non-Lunar legacy clients cannot be transferred at all.
- Relies on a Velocity-CTD internal. The fallback is resolved via reflection on
getProxyAddressToUse(). If a future CTD version renames it, the plugin logs a clear warning and performs no transfer (fail-safe) until updated.
Licensed under the GNU General Public License v3.0 — see LICENSE.txt.
You are free to use, modify, and fork this plugin, provided any redistributed version stays open source under the same license and keeps the original copyright notice. The ApolloPlayerTransfer name is not covered by the license: please rename your fork if you distribute a modified build.