Minecraft reverse proxy. Reads the handshake, routes on the client-supplied hostname, balances across a pool of backends, then splices the raw TCP stream. Standard library only.
go build -o bin/mcproxy ./cmd/proxy
go test ./...
./bin/mcproxy -config config.json
./bin/mcproxy -config config.json -check # validate and exit
listeners -> routes (matched by hostname) -> backends (balanced)
A listener is one TCP bind. A route attaches to one or more listeners and claims a set of hostnames. A route has one or more backends, so the same hostname can spread across several servers and ports.
"listen": ":25565" is shorthand for a single listener named default;
"backend": "host:port" is shorthand for a single-entry backends.
Matching order within a listener: exact host, then longest *.suffix, then the
* catch-all. A host may be claimed by only one route per listener, so the
same hostname can point at different backends on different ports. An unmatched
hostname gets an in-protocol disconnect (login) or an MOTD (server-list ping)
instead of a dead socket.
balance per route:
| value | behaviour |
|---|---|
round_robin |
default, honours weight |
least_conn |
fewest active sessions wins |
first |
strict order — pure failover |
Health is passive, nginx-style: max_fails consecutive dial failures eject a
backend for fail_timeout; the first success afterwards clears the streak.
A client is dialled against up to max_attempts backends before giving up —
safe, because nothing has been sent downstream yet. backup: true peers are
used only once every primary is ejected; if literally everything is ejected the
proxy still tries rather than black-holing the player.
Append a backend to a route, or a whole route:
{ "name": "skyblock", "listeners": ["public"], "hosts": ["sky.example.com"],
"backends": [{"addr": "10.0.0.13:25565"}, {"addr": "10.0.0.13:25566"}] }
Within reload_interval the file mtime is noticed, re-parsed and validated;
a bad config is rejected and the running routing table is kept. Live sessions
are never interrupted by a reload. Backend health and counters reset on reload.
listeners, admin_listen and max_conns need a restart.
admin_listen serves /healthz, /stats and /routes in plaintext, with
per-backend up/down, active sessions and byte counters. No auth — bind it to
localhost.
proxy_protocolprepends a PROXY v1 header so the backend sees the real IP. The backend must be configured to expect it.- Only the handshake and login-start packets are parsed; everything after is copied verbatim, so encryption and compression are untouched.
max_connsis process-wide, shared by all listeners. "# mcproxy-go"