Behind a reverse proxy
Give your community a permanent address such as chat.example.com, served through a reverse proxy you already run (Caddy, nginx, Traefik…). People join by typing the hostname. Relaying and peer discovery still use Moin's public defaults, so your server only runs the community itself.
Install the server first, or use Docker.
How it works
When someone adds chat.example.com, the app fetches https://chat.example.com/.well-known/moin/bootstrap. That small JSON document holds the server's ID, its UDP port, and which relay and discovery servers to use. The app then connects to the server over UDP, at the addresses your hostname resolves to.
So two paths have to reach the server:
| Path | Carries | Goes through |
|---|---|---|
| HTTPS on TCP 443 | the bootstrap document | your proxy, which ends TLS and forwards plain HTTP |
| One UDP port, e.g. 7443 | the members' connections to the server | straight to the server; proxies can't carry it |
Before you start
- A DNS record for your hostname, pointing at the public IP address that receives the UDP port. The app connects to every address the hostname resolves to, so don't publish an IPv6 (AAAA) record unless the UDP port works over IPv6 too.
- No CDN in front of the hostname. A CDN swaps your address for its own, and those don't carry Moin's UDP traffic. On Cloudflare, set the record to "DNS only".
- The UDP port forwarded to the machine running
moin-server, through your firewall and router.
1. Write the configuration
toml
name = "Game Night"
rooms = "managed"
[access]
bootstrap_secret = "correct-horse-battery-staple"
[server]
origin = "https://chat.example.com"
http_bind = "127.0.0.1:8080"
[server.coordinator]
bind = "0.0.0.0:7443"
public_port = 7443bootstrap_secretlets you claim the administrator role from the app; see the first administrator.originis the address people type, alwayshttps://. Include a port only if your proxy serves HTTPS on one other than 443.http_bindis where the server listens for your proxy. Use127.0.0.1when the proxy is on the same machine. If it's on another machine or in a container, bind an address the proxy can reach, such as0.0.0.0:8080, and keep that port closed to the internet.[server.coordinator]is the UDP port.bindis the local address and port;public_portis the port people reach from outside. They differ when your router forwards a different external port.
See Configuration for every setting under [server].
2. Configure the proxy
Forward /.well-known/moin/bootstrap to the server. Nothing else is required.
With Caddy, which also gets the certificate for you:
text
chat.example.com {
handle /.well-known/moin/bootstrap {
reverse_proxy 127.0.0.1:8080
}
}With nginx, inside the server block that has TLS for your hostname:
nginx
location = /.well-known/moin/bootstrap {
proxy_pass http://127.0.0.1:8080;
}The hostname can serve a website on every other path.
3. Start and check
sh
moin-server --config moin-server.tomlFrom another machine:
sh
curl https://chat.example.com/.well-known/moin/bootstrapYou should get a JSON document. A 503 means the server isn't ready yet, and clears once startup finishes. A 502 or 404 comes from the proxy and means it isn't reaching the server.
curl doesn't test the UDP port. If the bootstrap works but the app can't connect, check the UDP port forwarding and the firewall first.
4. Claim the community and invite people
In Moin, choose Add a connection and type chat.example.com. Then carry on as in the coordinator-only guide, all from the app:
- Claim the community with the bootstrap secret, to become its administrator.
- Create rooms.
- Invite people with Share community. Links carry your hostname, and people can also just type it.
- Decide who gets in, if the community shouldn't be open to anyone who has the link.
See Members and access for the details.
Optional: host more yourself
The public relay and discovery servers keep working for your community, so this setup is complete as it is. You can add Moin's own relay and discovery service behind the same proxy:
- Discovery (
[server.pkarr]): also forward/pkarr/to the server. It's ordinary HTTP. - Relay (
[server.relay]): also forward/relay,/ping,/generate_204and/healthz. The proxy must allow WebSocket upgrades and connections that stay open for hours. - Address probing (
[server.relay.qad]) uses its own UDP port with TLS, so it can't go through the proxy. It needs a certificate configured on the server itself; see Certificates.
To stop depending on the public servers entirely, see Self-contained.
