From e257349b377fe9fdb2c43b8d7ff89efde1a58b12 Mon Sep 17 00:00:00 2001 From: notplants <@notplants> Date: Thu, 4 Jun 2026 14:30:29 -0400 Subject: [PATCH] improve documentation of federation --- .env.sample | 4 ++++ README.md | 40 +++++++++++++++++++++++++++++++++++++--- compose.wellknown.yml | 24 ++++++++++++++++++++++++ 3 files changed, 65 insertions(+), 3 deletions(-) create mode 100644 compose.wellknown.yml diff --git a/.env.sample b/.env.sample index c3a7d5c..c319013 100644 --- a/.env.sample +++ b/.env.sample @@ -26,6 +26,10 @@ SECRET_REGISTRATION_VERSION=v1 # Set "true" to enable federation endpoint on $DOMAIN/.well-known/matrix/server SERVE_SERVER_WELLKNOWN=false +# Serve /.well-known/matrix/{server,client} on SERVER_NAME via Traefik. +# Can be used when SERVER_NAME != DOMAIN and SERVER_NAME is served by Traefik. +#COMPOSE_FILE="$COMPOSE_FILE:compose.wellknown.yml" + ALLOW_PUBLIC_ROOMS_FEDERATION=false ## Registration diff --git a/README.md b/README.md index 0a7459a..f5843ff 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,9 @@ Federation is on by default (`DISABLE_FEDERATION=0`). Remote homeservers need a #### Option 1: built-in well-known (`SERVER_NAME` = `DOMAIN`) -Set `SERVE_SERVER_WELLKNOWN=true` and leave `SERVER_NAME` unset (defaults to `DOMAIN`). The recipe's nginx serves `/.well-known/matrix/server` and `/.well-known/matrix/client` on `DOMAIN`. Suitable when users are e.g. `@alice:matrix.example.com`. +Set `SERVE_SERVER_WELLKNOWN=true` and leave `SERVER_NAME` unset (defaults to `DOMAIN`). The recipe's nginx serves `/.well-known/matrix/server` and `/.well-known/matrix/client` on `DOMAIN`. + +Suitable when users are e.g. `@alice:matrix.example.com`. #### Option 2: external well-known on `SERVER_NAME` @@ -55,11 +57,25 @@ DOMAIN=matrix.example.com SERVE_SERVER_WELLKNOWN=false ``` -Then configure whatever web service hosts `example.com` to serve: +The two paths that must be served on `SERVER_NAME` are: - `https://example.com/.well-known/matrix/server` → `{"m.server": "matrix.example.com:443"}` - `https://example.com/.well-known/matrix/client` → `{"m.homeserver": {"base_url": "https://matrix.example.com"}}` +**Recommended — let this recipe serve them via Traefik** by enabling `compose.wellknown.yml`: + +``` +COMPOSE_FILE="$COMPOSE_FILE:compose.wellknown.yml" +``` + +This publishes a Traefik router `Host(${SERVER_NAME}) && PathPrefix(/.well-known/matrix)` +pointing at the matrix nginx, which already serves both files. The path-scoped, high-priority +rule coexists with any apex website that also serves `Host(${SERVER_NAME})` — that site keeps +serving everything except `/.well-known/matrix`. `SERVER_NAME` must resolve to this Traefik so +ACME can issue its certificate. + +**Alternative** — serve the two files yourself from whatever already hosts `example.com`. + #### Option 3: Traefik `matrix-federation` entrypoint (port 8448) @@ -72,6 +88,23 @@ Requirements: With these in place, the recipe publishes a Traefik router on `Host(${SERVER_NAME})` via the `matrix-federation` entrypoint, reusing the existing matrix nginx → synapse path. +#### Option 4: DNS SRV records (usually not viable here) + +Federation can also be delegated with a DNS `SRV` record on `SERVER_NAME` instead of well-known: + +``` +_matrix-fed._tcp.example.com. 3600 IN SRV 10 0 8448 matrix.example.com. # modern +_matrix._tcp.example.com. 3600 IN SRV 10 0 8448 matrix.example.com. # deprecated, for older peers +``` + +The catch is TLS: on the SRV path a remote validates the certificate against **`SERVER_NAME`**, *not* the SRV target. This recipe's Traefik only issues a cert for **`DOMAIN`**, so: + +- **SRV → `DOMAIN`:443 fails** — the presented cert is for `DOMAIN`, but the peer requires one for `SERVER_NAME`. +- **SRV → `SERVER_NAME`:443 collides** — Traefik routes TLS by SNI, and `Host(SERVER_NAME)` on `:443` is already owned by whatever apex site serves `SERVER_NAME`. +- **SRV → `SERVER_NAME`:8448 works** — the Option 3 `matrix-federation` router holds a cert for `SERVER_NAME` — but that's just Option 3 made explicit (the `:8448` fallback already works with no SRV record). + +So I think SRV does little here. Probably prefer Option 2 (well-known). + #### Verifying The canonical test: @@ -93,7 +126,8 @@ curl https://DOMAIN/_matrix/key/v2/server ### Getting client discovery on a custom domain -You'll need to deploy something like [this](https://git.autonomic.zone/ruangrupa/well-known-uris). This could be implemented in this recipe but we haven't merged it in yet. Change sets are welcome. +Enable `compose.wellknown.yml` (see Option 2 above) — it serves `/.well-known/matrix/client` +on `SERVER_NAME` too, so clients signing in as `@alice:example.com` auto-discover the homeserver. ## Bridges For all Bridges: diff --git a/compose.wellknown.yml b/compose.wellknown.yml new file mode 100644 index 0000000..8ae966c --- /dev/null +++ b/compose.wellknown.yml @@ -0,0 +1,24 @@ +--- +version: "3.8" + +# Serve /.well-known/matrix/{server,client} on SERVER_NAME via Traefik, routed to +# the matrix nginx (`web`) — so server/client delegation works without hand-placing +# files on whatever else hosts SERVER_NAME. +# +# Enable when SERVER_NAME != DOMAIN (users are @alice:example.com, Synapse runs at +# matrix.example.com). The PathPrefix rule is more specific than a bare Host() +# router, and the explicit high priority guarantees it wins over any apex website +# that also serves Host(SERVER_NAME) — so the two coexist, the apex site keeps +# serving everything except /.well-known/matrix. +# +# Requires SERVER_NAME to resolve to this Traefik so ACME can issue its cert. +services: + web: + deploy: + labels: + - "traefik.http.routers.${STACK_NAME}-wellknown.rule=Host(`${SERVER_NAME}`) && PathPrefix(`/.well-known/matrix`)" + - "traefik.http.routers.${STACK_NAME}-wellknown.entrypoints=web-secure" + - "traefik.http.routers.${STACK_NAME}-wellknown.tls=true" + - "traefik.http.routers.${STACK_NAME}-wellknown.tls.certresolver=${LETS_ENCRYPT_ENV}" + - "traefik.http.routers.${STACK_NAME}-wellknown.service=${STACK_NAME}" + - "traefik.http.routers.${STACK_NAME}-wellknown.priority=1000"