From b81ef81a707eac3f975216b2d6a0b530b4630191 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9=20Lametti?=
Date: Wed, 4 Mar 2026 13:33:08 -0500
Subject: [PATCH] Docs: add example for root domain delegation
Although the procedure is documented in the example configuration file,
whether it is possible to use the root domain as the server name of a
tuwunel instance hosted on a subdomain, and a practical example of doing
so, is often requested in the unofficial support matrix room.
TODO:
- [ ] Add example for Caddy
- [ ] Add example for Traefik
---
docs/SUMMARY.md | 1 +
docs/deploying/generic.md | 4 +-
docs/deploying/root-domain-delegation.md | 118 +++++++++++++++++++++++
3 files changed, 121 insertions(+), 2 deletions(-)
create mode 100644 docs/deploying/root-domain-delegation.md
diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md
index f4d26a86..c859ce1c 100644
--- a/docs/SUMMARY.md
+++ b/docs/SUMMARY.md
@@ -8,6 +8,7 @@
- [Reverse Proxy - Caddy](deploying/reverse-proxy-caddy.md)
- [Reverse Proxy - Nginx](deploying/reverse-proxy-nginx.md)
- [Reverse Proxy - Traefik](deploying/reverse-proxy-traefik.md)
+ - [Example: root domain delegation](deploying/root-domain-delegation.md)
- [NixOS](deploying/nixos.md)
- [Docker](deploying/docker.md)
- [Kubernetes](deploying/kubernetes.md)
diff --git a/docs/deploying/generic.md b/docs/deploying/generic.md
index c724b9fc..d2c56ce1 100644
--- a/docs/deploying/generic.md
+++ b/docs/deploying/generic.md
@@ -73,7 +73,7 @@ Matrix's default federation port is port 8448, and clients must be using port 44
If you would like to use only port 443, or a different port, you will need to setup
delegation. Tuwunel has config options for doing delegation, or you can configure
your reverse proxy to manually serve the necessary JSON files to do delegation
-(see the `[global.well_known]` config section).
+(see the `[global.well_known]` config section and the [delegation example](root-domain-delegation.md)).
If Tuwunel runs behind a router or in a container and has a different public
IP address than the host system these public ports need to be forwarded directly
@@ -158,7 +158,7 @@ Regardless of which reverse proxy you choose, you will need to:
- `/_tuwunel/` - ad-hoc Tuwunel routes such as `/local_user_count` and `/server_version`
2. **Optionally reverse proxy (recommended):**
- - `/.well-known/matrix/client` and `/.well-known/matrix/server` if using Tuwunel to perform delegation (see the `[global.well_known]` config section)
+ - `/.well-known/matrix/client` and `/.well-known/matrix/server` if using Tuwunel to perform delegation (see the `[global.well_known]` config section and the [delegation example](root-domain-delegation.md))
- `/.well-known/matrix/support` if using Tuwunel to send the homeserver admin contact and support page (formerly known as MSC1929)
- `/` if you would like to see `hewwo from tuwunel woof!` at the root
diff --git a/docs/deploying/root-domain-delegation.md b/docs/deploying/root-domain-delegation.md
new file mode 100644
index 00000000..3e2276a0
--- /dev/null
+++ b/docs/deploying/root-domain-delegation.md
@@ -0,0 +1,118 @@
+# Example: using the root domain as the homeserver name
+
+[<= Back to Generic Deployment Guide](generic.md#quick-overview)
+
+It is possible to host tuwunel on a subdomain such as `matrix.example.com` but delegate from `example.com` as the server name. This means that usernames will be `@user:example.com` rather than `@user:matrix.example.com`.
+
+Federating servers and clients accessing tuwunel at `example.com` will attempt to discover the subdomain by accessing the `example.com/.well-known/matrix/client` and `example.com/.well-known/matrix/server` endpoints. These need to be set up to point back to `matrix.example.com`.
+
+> [!NOTE]
+> In all of the following examples, replace `matrix.example.com` with the subdomain where tuwunel is hosted, `` with the external port for federation, and `example.com` with the domain you want to use as the public-facing homeserver.
+
+## Configuration
+
+Make sure the following are set in your [configuration file](<../configuration/examples.md#:~:text=### Tuwunel Configuration>) or via [environment variables](../configuration.md#environment-variables):
+
+1. [Server name](<../configuration/examples.md#:~:text=# The server_name,#server_name>): set `TUWUNEL_SERVER_NAME=example.com` or in the configuration file:
+ ```toml,hidelines=~
+ [global]
+ ~
+ ~# The server_name is the pretty name of this server. It is used as a
+ ~# suffix for user and room IDs/aliases.
+ ~#
+ ~# See the docs for reverse proxying and delegation:
+ ~# https://tuwunel.chat/deploying/generic.html#setting-up-the-reverse-proxy
+ ~#
+ ~# Also see the `[global.well_known]` config section at the very bottom.
+ ~#
+ ~# Examples of delegation:
+ ~# - https://matrix.org/.well-known/matrix/server
+ ~# - https://matrix.org/.well-known/matrix/client
+ ~#
+ ~# YOU NEED TO EDIT THIS. THIS CANNOT BE CHANGED AFTER WITHOUT A DATABASE
+ ~# WIPE.
+ ~#
+ ~# example: "girlboss.ceo"
+ ~#
+ server_name = example.com
+ ```
+2. [Client-server URL](../configuration/examples.md#:~:text=#[global.well_known],#client): set `TUWUNEL_WELL_KNOWN__CLIENT=https://matrix.example.com` or in the configuration file:
+ ```toml,hidelines=~
+ [global.well_known]
+ ~
+ ~# The server URL that the client well-known file will serve. This should
+ ~# not contain a port, and should just be a valid HTTPS URL.
+ ~#
+ ~# example: "https://matrix.example.com"
+ ~#
+ client = https://matrix.example.com
+ ```
+3. [Server-server federation domain and port](<../configuration/examples.md#:~:text=# The server base domain,#server>): where `` is the external port for federation (default 8448, but often 443 when reverse proxying), set `TUWUNEL_WELL_KNOWN__SERVER=matrix.example.com:` or in the configuration file:
+ ```toml,hidelines=~
+ [global.well_known]
+ ~
+ ~# The server URL that the client well-known file will serve. This should
+ ~# not contain a port, and should just be a valid HTTPS URL.
+ ~#
+ ~# example: "https://matrix.example.com"
+ ~#
+ ~client = https://matrix.example.com
+ ~
+ ~# The server base domain of the URL with a specific port that the server
+ ~# well-known file will serve. This should contain a port at the end, and
+ ~# should not be a URL.
+ ~#
+ ~# example: "matrix.example.com:443"
+ ~#
+ server = matrix.example.com: # e.g. matrix.example.com:443
+ ```
+
+## Serving `.well-known` endpoints
+
+With the above configuration, tuwunel will generate and serve the appropriate `/.well-known/matrix` entries for delegation, so these can be served by reverse proxying `/.well-known/matrix` on `example.com` to tuwunel. Alternatively, if `example.com` is not behind a reverse proxy, static JSON files can be served directly.
+
+### Option 1: Static JSON files
+
+At a minimum, the following JSON files should be created:
+
+1. At `example.com/.well-known/matrix/client`:
+ ```json
+ {
+ "m.homeserver": {
+ "base_url": "https://matrix.example.com/"
+ }
+ }
+ ```
+2. At `example.com/.well-known/matrix/server` (substituting `` as above):
+ ```json
+ {
+ "m.server": "matrix.example.com:" // e.g. "matrix.example.com:443"
+ }
+ ```
+
+### Option 2: Reverse proxy
+
+This is an example configuration if `example.com` is reverse-proxied behing Nginx.
+
+> [!NOTE]
+> Replace `tuwunel` with the URL where tuwunel is listening; this may look like `127.0.0.1:8008`, `matrix.example.com`, or `tuwunel` if you declared an `upstream tuwunel` block.
+
+```nginx,hidelines=~
+server {
+ ~listen 443 ssl http2;
+ ~listen [::]:443 ssl http2;
+ server_name example.com;
+
+ location /.well-known/matrix {
+ proxy_pass http://tuwunel/.well-known/matrix;
+ proxy_set_header X-Forwarded-For $remote_addr;
+ proxy_ssl_server_name on;
+ }
+ ~
+ ~# The remainder of your nginx configuration for example.com including SSL termination, other locations, etc.
+}
+```
+
+## Testing
+
+Navigate to `example.com/.well-known/matrix/client` and `example.com/.well-known/matrix/server`. These should display results similar to the [JSON snippets above](#option-1-static-json-files).