Run Your Own Proxy
MUDBasher's proxy is open source. Point the app at your own copy and the background connection runs on a machine you control.
A proxy world in MUDBasher does not connect to your MUD from your phone. It connects to a small server that holds the telnet session for you, so iOS suspending the app does not kill the game connection. The connections guide explains why that is necessary.
By default that server is ours. It does not have to be. The proxy is a separate open source
project, mud-web-proxy, released
under GPL-3.0-or-later, and MUDBasher will talk to any instance of it you can reach over
wss://. MUD administrators run one for their own game. Privacy-minded players run
one on a VPS so their session never crosses our infrastructure.
The Two Setups
The shape is identical either way: three hops, two sockets, one persistent telnet connection held off your device. The only thing that moves is who operates the middle box.
Default: our proxy
Nothing to set up. Push alerts and silent-push background sync work. Your telnet traffic passes through a machine we run.
Self-hosted: your proxy
You supply the host, the domain and the certificate. Sessions survive backgrounding and replay when you return, but push notifications do not work. Your telnet traffic never touches our infrastructure.
What Changes When the Proxy Is Yours
MUDBasher treats proxies it does not recognise more cautiously than its own, and that costs you two features. Worth knowing before you spend an evening on it.
Our proxy
- Session survives backgrounding
- Buffered output replayed on return
- Push notifications for events
- Silent-push background sync
- App Attest on every connection
- We keep it patched and running
- Traffic passes through our host
Your proxy
- Session survives backgrounding
- Buffered output replayed on return
- No push notifications
- No silent-push background sync
- No App Attest
- You patch it and keep it running
- Traffic passes through your host
For the same reason, App Attest is skipped for a self-hosted proxy: the app only attests to
hosts it knows are ours. Leave REQUIRE_APP_AUTH off on your instance, or MUDBasher
will be refused at the handshake.
What You Need
- A host. A small Linux VPS is plenty. The proxy is a single process holding one socket pair per player.
- A domain name pointing at it. iOS requires
wss://, andwss://requires a certificate, which requires a name. MUDBasher rejects plainws://for anything but localhost. - Inbound ports 80 and 443. Traffic runs on 443, but certificate issuance and renewal need 80. A firewall that only opens 443 is the usual cause of a certificate that quietly stops renewing.
- Docker with Compose, or Bun. Two supported paths. Both put Caddy in front to terminate TLS and get certificates automatically.
Sessions live in memory only. Restarting the proxy to upgrade it disconnects everyone connected to it, and they reconnect from scratch. There is no handover.
Setting It Up
The repository is the authority on the commands, and it stays in step with the code in a way this page cannot. What follows is the shape of the job, so you know what it involves before you start.
- 1. Get the code
- Clone mud-web-proxy and
check out the current release rather than
main. - 2. Set four values
- Copy the example environment file and fill in your domain, an email address for certificate notices, and the host and port of the MUD you want to reach. The examples ship empty on purpose, so an unedited copy fails immediately with a named error instead of starting on placeholder settings.
- 3. Start it
- Either Docker Compose, which is the portable path, or Bun under systemd, which is the native single-VM path.
- 4. Check it
- Request
/healthover HTTPS. A healthy instance answers with a status ofhealthyand the version actually running. If that works from a browser, the certificate and both ports are right.
The four values that matter
MWP_DOMAIN is the domain pointing at this host, e.g. proxy.example.com
MWP_ACME_EMAIL is where Let's Encrypt sends expiry notices
TN_HOST is the MUD to connect to, e.g. mud.example.com
TN_PORT is its port, e.g. 4000
Everything else has a default. The configuration reference covers every variable.
TN_HOST and TN_PORT you configured, and refuses
anything else with a message naming the target it does allow. Your MUDBasher world's hostname
and port must match those two values exactly. If you want one proxy to serve several
worlds, switch it to allowlist mode and name each host:port you intend to reach.
See target
policy before you widen it further than that.
Pointing MUDBasher at It
Per world, not globally. You can keep some worlds on our proxy, move others to yours, and leave the rest on a direct connection.
- Edit the world and find Connection Mode.
- Set it to Proxy. A Proxy URL field appears, pre-filled with our proxy.
- Replace it with your own, as
wss://proxy.example.com. - Check the world's hostname and port still match the
TN_HOSTandTN_PORTyour proxy allows. - Save. The change applies on the next connection.
The field will not accept a URL that is not wss://, other than
ws://localhost for testing against a proxy on your own machine. The session
resume message carries an auth token, so cleartext to a remote host is refused rather than
warned about.
If It Will Not Connect
- The app rejects the URL as you type it. It is not
wss://, or the host is empty. Only loopback may usews://. - It connects, then closes immediately. Check the proxy's logs. A close from
the proxy carries a reason naming the HTTP status it corresponds to, and the two you are
likely to see are
401, meaning it wants authentication MUDBasher cannot supply, and403, meaning an origin restriction is rejecting the app. - It refuses your MUD by name. The proxy is in fixed-target mode and your
world's hostname and port do not match
TN_HOSTandTN_PORTexactly. - 401 on every attempt. Leave
AUTH_MODEatnoneandREQUIRE_APP_AUTHoff. MUDBasher has no field for a shared secret, and it does not attest to proxies it does not recognise as ours. - 403 on every attempt. Leave
ALLOWED_ORIGINSunset. It exists to constrain browsers; a native app sends no origin header, and setting the variable makes an absent origin a refusal. - Certificate errors after a few months. Port 80 is closed. Renewal needs it even though traffic runs on 443.
- Everyone drops at once. The proxy restarted. Sessions are in memory and do not survive it.
The operations guide has a troubleshooting entry for every startup error the proxy can refuse to start with, and the project tests that this stays true.
Docs and Help
| You want | Go to |
|---|---|
| The install commands, start to finish | README |
| The container path in full | Docker Compose deployment |
| The single-VM path in full | Native systemd deployment |
| Every setting and its default | Configuration reference |
| What this exposes, and what it does not protect | Security model |
| Certificates, upgrades, logs, health | Operations |
| To ask a question | Discussions |
| To report a bug in the proxy | Issues |
Problems with the proxy itself belong in that repository. Problems with the app talking to it
belong on the support page. Tell us the proxy URL scheme you used
and whether /health answers from a browser.
In short
Self-hosting moves the persistent telnet connection onto hardware you control and keeps your game traffic off ours. You keep background session survival and replay. You give up push notifications and background sync, and you take on a certificate, a host to patch, and a restart that disconnects your players.
If that trade suits you, the README is where to start.
MUDBasher, self-hosted proxy guide.