From 59ad89cfb46de63e0c5f4f8724e84c3c331f4c56 Mon Sep 17 00:00:00 2001 From: notplants <@notplants> Date: Mon, 25 May 2026 15:52:16 +0000 Subject: [PATCH 1/4] add traefik labels for matrix-federation entrypoint MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a second Traefik router on the web (nginx) service that listens on the `matrix-federation` entrypoint (host port 8448 via the traefik recipe's compose.matrix.yml) and uses Host(${SERVER_NAME}). Lets remote homeservers reach this server's federation/key endpoints via the standard `:8448` fallback when no .well-known or SRV delegation exists for SERVER_NAME — useful when SERVER_NAME differs from DOMAIN and no external service serves /.well-known/matrix/server. Requires MATRIX_FEDERATION_ENABLED=1 on the traefik stack. --- compose.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/compose.yml b/compose.yml index a9209f6..eaab589 100644 --- a/compose.yml +++ b/compose.yml @@ -29,6 +29,11 @@ services: - "traefik.http.routers.${STACK_NAME}.rule=Host(`${DOMAIN}`)" - "traefik.http.routers.${STACK_NAME}.entrypoints=web-secure" - "traefik.http.routers.${STACK_NAME}.tls.certresolver=${LETS_ENCRYPT_ENV}" + - "traefik.http.routers.${STACK_NAME}-federation.rule=Host(`${SERVER_NAME}`)" + - "traefik.http.routers.${STACK_NAME}-federation.entrypoints=matrix-federation" + - "traefik.http.routers.${STACK_NAME}-federation.tls=true" + - "traefik.http.routers.${STACK_NAME}-federation.tls.certresolver=${LETS_ENCRYPT_ENV}" + - "traefik.http.routers.${STACK_NAME}-federation.service=${STACK_NAME}" healthcheck: test: curl -f http://${STACK_NAME}_app:8008/health || exit 1 interval: 30s From b730cadb06396ebe761f34bdb36d003d36fbe92c Mon Sep 17 00:00:00 2001 From: notplants <@notplants> Date: Mon, 25 May 2026 16:59:50 +0000 Subject: [PATCH 2/4] docs: document the three federation setup options --- README.md | 52 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index a76dc78..0a7459a 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,57 @@ ### Enabling federation -See [`#27`](https://git.coopcloud.tech/coop-cloud/matrix-synapse/pulls/27) for more. Depending on your setup, using `SERVE_SERVER_WELLKNOWN=true` might work to start federating. Make sure you don't leave `DISABLE_FEDERATION=1` set! +Federation is on by default (`DISABLE_FEDERATION=0`). Remote homeservers need a way to discover the host:port that serves your `SERVER_NAME`. There are three supported approaches. + +#### 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`. + +#### Option 2: external well-known on `SERVER_NAME` + +Use when you want users to be e.g. `@alice:example.com` while Synapse runs at `matrix.example.com`. Set: + +``` +SERVER_NAME=example.com +DOMAIN=matrix.example.com +SERVE_SERVER_WELLKNOWN=false +``` + +Then configure whatever web service hosts `example.com` to serve: + +- `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"}}` + + +#### Option 3: Traefik `matrix-federation` entrypoint (port 8448) + +Use when `SERVER_NAME` ≠ `DOMAIN` but you have no separate web service at `SERVER_NAME`. Remote homeservers fall back to `SERVER_NAME:8448` when there's no delegation. + +Requirements: + +- [traefik](https://git.coopcloud.tech/coop-cloud/traefik) `>= 5.1.2+v3.6.15` with `MATRIX_FEDERATION_ENABLED=1` and `compose.matrix.yml` enabled. +- `SERVER_NAME` set in your matrix-synapse env (used by the federation router's Host rule). + +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. + +#### Verifying + +The canonical test: + +- https://federationtester.matrix.org/#YOUR_SERVER_NAME + +Or check the underlying paths directly. They should all return JSON: + +```bash +# Options 1 & 2 — delegation +curl https://SERVER_NAME/.well-known/matrix/server + +# Option 3 — federation endpoint via 8448 +curl https://SERVER_NAME:8448/_matrix/key/v2/server + +# Confirms Synapse itself is healthy (independent of the path remote servers use) +curl https://DOMAIN/_matrix/key/v2/server +``` ### Getting client discovery on a custom domain From e257349b377fe9fdb2c43b8d7ff89efde1a58b12 Mon Sep 17 00:00:00 2001 From: notplants <@notplants> Date: Thu, 4 Jun 2026 14:30:29 -0400 Subject: [PATCH 3/4] 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" From f836c7438641eaeb9c70833b67ef640b8781793d Mon Sep 17 00:00:00 2001 From: notplants <@notplants> Date: Mon, 8 Jun 2026 14:03:16 -0400 Subject: [PATCH 4/4] improvements to readme --- .env.sample | 2 ++ README.md | 13 +++++++++---- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/.env.sample b/.env.sample index c319013..fe6fde9 100644 --- a/.env.sample +++ b/.env.sample @@ -23,6 +23,8 @@ SECRET_REGISTRATION_VERSION=v1 #DISABLE_FEDERATION=1 +# SERVE_SERVER_WELLKNOWN only works if SERVER_NAME and DOMAIN are the same +# if they are different, then a different federation method is needed (like compose.wellknown.yml) # Set "true" to enable federation endpoint on $DOMAIN/.well-known/matrix/server SERVE_SERVER_WELLKNOWN=false diff --git a/README.md b/README.md index f5843ff..755fbbb 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,8 @@ ### Enabling federation -Federation is on by default (`DISABLE_FEDERATION=0`). Remote homeservers need a way to discover the host:port that serves your `SERVER_NAME`. There are three supported approaches. +Federation is on by default (`DISABLE_FEDERATION=0`). Remote homeservers need a way to discover the host:port that serves your `SERVER_NAME`. +There are three supported approaches. At least one needs to be working for federation to work (and matrix will fallback between them). #### Option 1: built-in well-known (`SERVER_NAME` = `DOMAIN`) @@ -49,7 +50,7 @@ Suitable when users are e.g. `@alice:matrix.example.com`. #### Option 2: external well-known on `SERVER_NAME` -Use when you want users to be e.g. `@alice:example.com` while Synapse runs at `matrix.example.com`. Set: +Use when you want users to be e.g. `@alice:example.com` while Synapse runs at `matrix.example.com` (and SERVER_NAME is served by the same machine that Synapse is running on). Set: ``` SERVER_NAME=example.com @@ -79,7 +80,7 @@ ACME can issue its certificate. #### Option 3: Traefik `matrix-federation` entrypoint (port 8448) -Use when `SERVER_NAME` ≠ `DOMAIN` but you have no separate web service at `SERVER_NAME`. Remote homeservers fall back to `SERVER_NAME:8448` when there's no delegation. +Use when `SERVER_NAME` ≠ `DOMAIN` but you have no separate web service at `SERVER_NAME`. Remote homeservers fall back to `SERVER_NAME:8448` when there's no delegation (also requires SERVER_NAME pointing to same server that matrix is running on). Requirements: @@ -88,8 +89,13 @@ 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) +For reasons explained below, I might be confused, but I think SRV records usually don't help with co-op cloud matrix deployments. + +You should probably prefer Option 2 (well known), but the possibility of SRV is explained below: + Federation can also be delegated with a DNS `SRV` record on `SERVER_NAME` instead of well-known: ``` @@ -103,7 +109,6 @@ The catch is TLS: on the SRV path a remote validates the certificate against **` - **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