# mchost
Run a Minecraft server jar and expose it through a **Cloudflare Tunnel** — no port
forwarding, no public IP.
Two tunnel modes:
| Mode | Flag | Address players see | Cloudflare account needed? |
|------|------|---------------------|----------------------------|
| Quick tunnel | *(default)* | a random `something-words.trycloudflare.com` name, new on every run | no |
| Named tunnel | `--token <TOKEN>` | the domain you bought and pointed at Cloudflare | yes (token from the Zero Trust dashboard) |
## How players join
Cloudflare's tunnel edge only speaks HTTP(S), not raw Minecraft TCP, so mchost
runs a small **WebSocket bridge** and points the tunnel at it. Players connect
through the tunnel with either:
- the **Fabric client mod** (in-game: Multiplayer → *Tunnel...* → paste the
invite), or
- the **CLI helper** `mchost connect <invite>` — same thing without the mod.
Both accept one shareable invite string that mchost prints (and writes to
`mchost-address.txt` for the server mod to show in-game):
```
┌──────────────┐ vanilla MC ┌────────────────┐ WebSocket over TLS ┌────────┐ ws ┌────────┐
│ MC client │─────────────▶│ mod / helper │─────────────────────▶│tunnel │─────▶│ bridge │──▶127.0.0.1:25565
│127.0.0.1:25565│ │ (player runs) │ wss://<tunnel-url> │ edge │ │(server)│
└──────────────┘ └────────────────┘ └────────┘ └────────┘
```
They paste one line and play in their normal vanilla client — no port
forwarding, no public IP.
## Build
```sh
cargo build --release # binary: target/release/mchost
```
Requires: Rust to build, Java 21+ to run a modern server jar, and the
`cloudflared` binary on your `PATH` (or pass `--cloudflared <path>`).
## Quick start (with the bundled Fabric server)
If you've installed the Fabric server into `server/` (see [mod/README.md](mod/README.md)):
```sh
./start.sh # builds mchost if needed, starts server/ behind a
# quick tunnel with a random invite secret
SECRET=hunter2 ./start.sh # stable invite secret
TOKEN=eyJh... PUBLIC_HOST=mc.example.com ./start.sh # your own domain
./start.sh --no-tunnel # local only
```
The first run writes `eula=true` to `server/eula.txt` (accepting
[Minecraft's EULA](https://aka.ms/MinecraftEULA) on your behalf — only run it
if you're fine with that). `DRY_RUN=1 ./start.sh` prints the command instead.
## Usage
```sh
# Quick tunnel: random trycloudflare.com name, no account
mchost server.jar
# Accept the Minecraft EULA for you and give the JVM 4G
mchost -m 4G --eula server.jar
# Named tunnel: served from your purchased domain (see setup below)
mchost server.jar --token eyJhIjoiXXXXXXXX...
# Local only
mchost --no-tunnel server.jar
# Extra arguments for the server jar go after --
mchost paper.jar -- -XX:+UseG1GC
# Player side: share the tunnel as a local TCP port for a vanilla client
mchost connect https://funny-name.trycloudflare.com --secret my-secret
# ...or paste the short invite mchost prints:
mchost connect 'mchost://funny-name.trycloudflare.com/mc?s=my-secret'
```
The server prints the exact invite to share with players whenever the tunnel
comes up (and again when you type `tunnel`). The same invite is written to
`mchost-address.txt` in the server directory, which the [Fabric server mod](mod)
turns into a click-to-copy chat message in-game.
If you don't name a jar, mchost picks the newest server-looking jar in the
server directory. The port is read from `server.properties` (`server-port`,
default 25565) or forced with `--port`.
Everything the server prints shows up prefixed with `[server]`, everything
cloudflared prints with `[tunnel]`. While it runs you can type into the console:
- `stop` — gracefully stop the server (same as typing `stop` in the MC console)
- `tunnel` — show the current tunnel address
- `help` — list commands
- anything else — forwarded straight to the Minecraft server console
`Ctrl-C` does a graceful stop too (second `Ctrl-C` forces). A quick tunnel that
drops is restarted automatically with a fresh random name (up to `--restarts`,
default 5).
## Named tunnel setup (purchased domain)
1. Buy a domain and add it to Cloudflare (its DNS must be on Cloudflare).
2. In the [Zero Trust dashboard](https://one.dash.cloudflare.com) go to
**Networks → Tunnels → Create a tunnel**, choose **Cloudflared**, name it.
3. Copy the token from the install command Cloudflare shows you — that's the
`--token` value for mchost.
4. Still in the tunnel's configuration, add a **Public Hostname**:
- Subdomain/Domain: e.g. `mc.example.com`
- Service: type **HTTP**, URL `http://localhost:8081` — the WebSocket bridge
mchost runs (use your `--bridge-port` if you changed it)
5. Run `mchost server.jar --token <TOKEN>`. mchost just runs
`cloudflared tunnel run --token ...`; Cloudflare routes
`mc.example.com` to your server, and players run
`mchost connect https://mc.example.com --secret ...` as usual.
(If you run with `--no-bridge` and Cloudflare Spectrum, use service type **TCP**
and `localhost:25565` instead — then players connect directly, no helper.)
## How it works (and why a bridge)
Cloudflare's tunnel edge is HTTP-only: a vanilla Minecraft client cannot speak
raw TCP to a `*.trycloudflare.com` name or a tunnelled custom domain. mchost
handles that end to end:
1. It runs a **WebSocket bridge** on `127.0.0.1:8081` that maps each WebSocket
session onto the local Minecraft TCP port (`--bridge-port` to change,
`--no-bridge` to disable).
2. It points the tunnel at the bridge (`http://127.0.0.1:8081`).
3. `mchost connect <url>` (player side) opens a WebSocket to the tunnel and
listens on `127.0.0.1:25565`, so the player's **unmodified** Minecraft
client just joins `127.0.0.1`. Every local connection gets its own tunnel
session, so several players behind one helper can play together
(`--bind 0.0.0.0` opens it to the whole LAN).
Practical notes:
- **Use `--bridge-secret`** (and pass it with `--secret` on the player side).
Quick-tunnel URLs are public-ish; the secret makes the bridge answer 401 to
strangers and the helper fails fast with a clear message. As always, real
protection is `online-mode=true` in `server.properties`.
- Right after a quick tunnel starts, its random name can take a few seconds to
appear in DNS. The helper retries on its own and reports progress.
- The WebSocket rides an HTTP/1.1 upgrade. Generic HTTP tools that default to
HTTP/2 (curl, for instance) will get a 502 from the bridge — that is the edge,
not a bug; the helper speaks HTTP/1.1 and works.
- If you want vanilla-direct connections with no helper, you need one of:
**Cloudflare Spectrum** (paid TCP proxying) with `--no-bridge`, a
**Zero Trust private network + WARP** on the players' side, or a TCP tunnel
tool built for it (playit.gg, ngrok TCP, a VPS + Tailscale).
## Minecraft mods (Fabric)
[`mod/`](mod) contains two optional Fabric mods for Minecraft 26.3 that make
sharing fully in-game:
- **mchost client** — a *Tunnel...* button in the Multiplayer screen: players
paste the invite and join with a vanilla client, no CLI needed. Remembers
the last invite.
- **mchost server** — shows the invite **in chat, click-to-copy**: when mchost
publishes it, when an operator joins, and via `/mchost`.
Build with `./gradlew build` in each folder (JDK 25, see [mod/README.md](mod/README.md)).
Use `--public-host mc.example.com` with `--token` so the invite uses your own
domain.
## Options
```
mchost [SERVER_JAR] [options]
-j, --jar <PATH> Server jar to run (auto-detected when omitted)
-d, --dir <PATH> Server working directory (default: the jar's directory)
--java <PATH> Java executable (default: java)
-m, --memory <SIZE> JVM heap, e.g. 2G or 4096M (default: 2G)
-p, --port <PORT> Minecraft port (default: server.properties, else 25565)
--eula Write eula=true into eula.txt (accept the Minecraft EULA)
--token <TOKEN> Cloudflare Tunnel token (named tunnel on your domain)
--no-tunnel Run without any tunnel
--cloudflared <PATH> Path to the cloudflared binary
--restarts <N> Quick-tunnel restart budget (default: 5)
--bridge-port <PORT> Local port of the WebSocket bridge (default: 8081)
--bridge-secret <S> Require this secret from joining players
--no-bridge Disable the bridge (Spectrum/TCP-style setups only)
-h, --help Show help
-V, --version Show version
mchost connect <URL> [options]
--local-port <PORT> Local TCP port for the vanilla client (default: 25565)
--bind <ADDR> Local address to listen on (default: 127.0.0.1)
--secret <SECRET> The server's --bridge-secret value, if it has one
```