Matrix Well-Known Domain #

Hagency does not require you to point your domain at our DNS, edge, or workspace host.

If you want users to sign in with a domain you control, such as @alice:example.com, you only need to publish the standard Matrix discovery files on that domain:

  • /.well-known/matrix/server
  • /.well-known/matrix/client

Your existing website and DNS for example.com can stay exactly where they are. The only requirement is that these two HTTPS URLs exist and return the JSON generated in the dashboard.

What this changes #

  • Matrix ID domain — the domain shown in user IDs, for example @alice:example.com
  • Actual homeserver URL — the current workspace homeserver URL already shown by Hagency
  • How clients connect — Matrix clients read /.well-known on your domain, then connect to the homeserver URL from that JSON

1. Set the Matrix ID domain #

  1. Open Settings → Workspace → Matrix Server Name.
  2. Enter the domain you want users to see in their Matrix IDs, for example example.com.
  3. Save the change.

Important:

  • this value becomes the Matrix server_name
  • set it before creating your first real Matrix user
  • once the first human Matrix user exists, changing it is locked

2. Publish /.well-known #

  1. Stay on Settings → Workspace and open the Matrix Well-Known Domain section.
  2. Download the generated JSON files for the current workspace.
  3. Publish them on your own domain at:
    • https://example.com/.well-known/matrix/server
    • https://example.com/.well-known/matrix/client

Your domain does not need any special A, CNAME, or TXT records that point to Hagency.

You can host these files from:

  • your existing website
  • a static hosting service
  • a reverse proxy in front of your current site
  • any HTTPS server that answers on your domain

Important: browser clients also need the right response headers #

If you use a browser-based Matrix client such as https://app.element.io, it is not enough that the JSON opens in your browser.

The browser reads https://example.com/.well-known/matrix/client as a cross-origin request. Even if you can see the JSON in the address bar, the client can still treat autodiscovery as failed if the response headers are wrong.

At minimum, make sure:

  • /.well-known/matrix/client returns Content-Type: application/json
  • /.well-known/matrix/client returns Access-Control-Allow-Origin: *
  • the safest option is to return Content-Type: application/json and Access-Control-Allow-Origin: * on /.well-known/matrix/server too

Common mistakes:

  • "I can open the URL and see JSON, so it must be fine." Not necessarily. A direct browser navigation is not the same thing as a successful cross-origin fetch.
  • "The dashboard validation passed, so clients must be fine." Not necessarily. Server-side validation checks the URLs and JSON values; browser clients still depend on CORS.
  • "The client tried https://example.com/_matrix/client/versions, so base_url should probably be https://example.com." Usually not. A more common explanation is that the client failed to read /.well-known/matrix/client, then fell back to probing example.com as if it were a direct homeserver.

m.homeserver.base_url should keep using the real workspace homeserver URL generated by the dashboard, typically something like https://<workspace>.hagency.cloud. Do not rewrite base_url to your vanity domain unless you are actually serving the Matrix Client-Server API itself on example.com/_matrix/client/*.

After publishing, check the response headers directly:

bash
curl -i https://example.com/.well-known/matrix/client
curl -i https://example.com/.well-known/matrix/server

At minimum, you should see:

  • HTTP/1.1 200 or HTTP/2 200
  • Content-Type: application/json
  • Access-Control-Allow-Origin: *
  • a body that exactly matches the JSON currently downloaded from the dashboard

Mainstream reverse-proxy examples #

All examples below assume:

  • your main site or app keeps running normally
  • only /.well-known/matrix/client and /.well-known/matrix/server are intercepted specially
  • the JSON files already exist on local disk

Caddy #

caddyfile
example.com {
    root * /srv/well-known

    @matrix_well_known path /.well-known/matrix/client /.well-known/matrix/server
    handle @matrix_well_known {
        header Content-Type application/json
        header Access-Control-Allow-Origin *
        file_server
    }

    reverse_proxy 127.0.0.1:8080
}

Place the two files at:

  • /srv/well-known/.well-known/matrix/client
  • /srv/well-known/.well-known/matrix/server

Nginx #

nginx
server {
    listen 443 ssl http2;
    server_name example.com;

    root /var/www/example.com;

    location = /.well-known/matrix/client {
        default_type application/json;
        add_header Access-Control-Allow-Origin * always;
        try_files $uri =404;
    }

    location = /.well-known/matrix/server {
        default_type application/json;
        add_header Access-Control-Allow-Origin * always;
        try_files $uri =404;
    }

    location / {
        proxy_pass http://127.0.0.1:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

The two files live at:

  • /var/www/example.com/.well-known/matrix/client
  • /var/www/example.com/.well-known/matrix/server

Apache HTTP Server #

With mod_proxy, mod_proxy_http, mod_alias, and mod_headers enabled, a typical setup looks like:

apache
<VirtualHost *:443>
    ServerName example.com

    ProxyPreserveHost On

    ProxyPass /.well-known/matrix/ !
    Alias /.well-known/matrix/ "/var/www/example.com/.well-known/matrix/"

    <Directory "/var/www/example.com/.well-known/matrix">
        Require all granted
    </Directory>

    <LocationMatch "^/\.well-known/matrix/(client|server)$">
        ForceType application/json
        Header always set Access-Control-Allow-Origin "*"
    </LocationMatch>

    ProxyPass / http://127.0.0.1:8080/
    ProxyPassReverse / http://127.0.0.1:8080/
</VirtualHost>

Traefik #

Traefik itself only routes traffic and adds headers; you still need a backend service that actually serves the two JSON files, such as a tiny static Nginx container. Here is an example label set for that static service:

yaml
labels:
  - "traefik.enable=true"
  - "traefik.http.routers.example-matrix-wellknown.rule=Host(`example.com`) && (Path(`/.well-known/matrix/client`) || Path(`/.well-known/matrix/server`))"
  - "traefik.http.routers.example-matrix-wellknown.priority=100"
  - "traefik.http.routers.example-matrix-wellknown.entrypoints=websecure"
  - "traefik.http.routers.example-matrix-wellknown.tls=true"
  - "traefik.http.routers.example-matrix-wellknown.middlewares=matrix-wellknown-headers"
  - "traefik.http.routers.example-matrix-wellknown.service=matrix-wellknown"
  - "traefik.http.middlewares.matrix-wellknown-headers.headers.customresponseheaders.Content-Type=application/json"
  - "traefik.http.middlewares.matrix-wellknown-headers.headers.customresponseheaders.Access-Control-Allow-Origin=*"
  - "traefik.http.services.matrix-wellknown.loadbalancer.server.port=80"

Your main application keeps its existing router for everything else; only /.well-known/matrix/* should hit this static service.

3. Validate the published files #

After publishing the files, go back to the dashboard and click Validate Published Files.

The validation checks:

  • the domain returns the expected /.well-known/matrix/server
  • the domain returns the expected /.well-known/matrix/client
  • the generated values still point at your current workspace homeserver

Example layout #

  • current Hagency workspace host stays unchanged
  • Matrix ID domain is example.com
  • https://example.com/.well-known/matrix/server points clients back to the workspace homeserver
  • https://example.com/.well-known/matrix/client points clients back to the workspace homeserver base URL

In other words, your domain only acts as the public Matrix identity and discovery entrypoint. It does not need to send normal web traffic to Hagency.

Troubleshooting #

  • Matrix /.well-known validation failed — make sure both files are published on the exact paths and the JSON matches the current values shown in the dashboard.
  • Dashboard validation passes but app.element.io still fails — check whether /.well-known/matrix/client really returns Content-Type: application/json and Access-Control-Allow-Origin: *.
  • The client requests https://example.com/_matrix/client/versions — this usually means client-side autodiscovery failed. Do not change m.homeserver.base_url first; check CORS and Content-Type on /.well-known/matrix/client.
  • The domain still points to your old homeserver URL — re-download the files after changing workspace settings, then publish the new JSON.
  • Your website already uses /.well-known — merge the Matrix files into the same /.well-known directory; you do not need to move the whole site.
  • You were expecting a DNS step — there is none for this flow. Do not add A, CNAME, or TXT records to point the domain at Hagency unless you are running some separate DNS change outside this guide.