This guide will walk you through deploying a powerful, federated Matrix Synapse homeserver. We will use the popular matrix-docker-ansible-deploy playbook, which simplifies the setup of Synapse and its various components, including bridges for other chat platforms.

This setup is designed to integrate seamlessly with an existing Traefik v3 reverse proxy and a root-domain Nginx web server, making it a perfect addition to a modern, container-based infrastructure.

Changelog

DateChange
2026-07-01Migration Notes: Added homeserver identity, bridge-state, MatrixRTC discovery, and media-retention guidance.
2026-06-12Playbook Compatibility: Refreshed the guide for current Ketesa, Telegram bridgev2, migration validation, Docker collection requirements, and a safer baseline without Matrix RTC.
2026-02-20Configuration Update: Updated matrix_coturn_enabled to coturn_enabled, removed Sliding Sync, and added PostgreSQL upgrade instructions.
2025-09-17Initial Version: Guide created, focusing on Traefik v3 integration, Ansible deployment, and enabling various bridges and features like Sliding Sync.

1. Prerequisites

  1. Traefik v3 and CrowdSec with Docker Compose: A Modern Security Stack: This is the foundation for our reverse proxy and security.
  2. Deploying a Secure Nginx Website with Traefik and Docker Compose: This step is crucial as it establishes a web server on your root domain and correctly configures the .well-known/matrix delegation required for federation.
⚠️ HARD REQUIREMENT

The following steps will not work correctly without the Traefik and Nginx stacks running as described in the prerequisite guides.

ℹ️ ROOT DOMAIN REMAINS FREE FOR YOUR WEBSITE

Synapse runs on a dedicated subdomain (e.g., matrix.your-domain.com). The root domain (e.g., your-domain.com) continues to serve your main website via Nginx. The .well-known files on the root domain merely delegate clients and federation to the subdomain, so your homepage remains unaffected.

Architecture (simplified)

  Users / Browsers / Homeservers


      your-domain.com  ──>  [Nginx](../nginx_webserver/) on root domain
             │                 │
             │                 ├─ Serves your website (/, /assets, ...)
             │                 └─ Serves .well-known/matrix/{server,client}
             │                                  │
             │                                  ▼
             └────────────────────────── delegates to ──> matrix.your-domain.com
                                                       (Synapse via Traefik)


                                               Traefik (websecure, synapse)


                                                        Synapse

You will also need:

  • git, jq, and pwgen for version control, JSON parsing, and password generation.
  • Python 3 and pip installed on your server.
  • sudo or root access.

Install the required tools:

sudo apt update && sudo apt -y install git jq pwgen python3-pip python3-venv make

2. Directory Structure

First, we’ll create a dedicated directory for the Matrix playbook configuration and files.

sudo mkdir -p /opt/containers/matrix

3. Download the Ansible Playbook

Next, clone the official matrix-docker-ansible-deploy repository from GitHub into the directory you just created.

sudo git clone https://github.com/spantaleev/matrix-docker-ansible-deploy.git /opt/containers/matrix

4. Initial Configuration

The playbook is configured using Ansible variables. We’ll start by creating a configuration directory based on your Matrix server’s hostname and copying the example vars.yml file.

ℹ️ HOSTNAME CONVENTION

The playbook expects the server to be available at matrix.your-domain.com. The configuration directory must match this hostname.

cd /opt/containers/matrix

# Replace 'matrix.your-domain.com' with your actual server name
sudo mkdir -p inventory/host_vars/matrix.your-domain.com
sudo cp examples/vars.yml inventory/host_vars/matrix.your-domain.com/

4.1. Generate Secret Keys

The configuration requires two strong secret keys. You can generate these using pwgen or openssl.

# Generate two strong keys and save them for the next step
pwgen -s 64 1
pwgen -s 64 1

4.2. Customize vars.yml

Now, open the vars.yml file and customize it for your environment. This is the most critical step.

sudo nano inventory/host_vars/matrix.your-domain.com/vars.yml

Below is a complete configuration based on a feature-rich setup. Adjust the values to match your domain, secrets, and desired features.

# Acknowledge the playbook breaking changes reviewed for this guide.
matrix_playbook_migration_validated_version: v2026.05.18.0

# The bare domain name which represents your Matrix identity.
matrix_domain: your-domain.com
matrix_homeserver_implementation: synapse
matrix_homeserver_generic_secret_key: 'PASTE-YOUR-GENERIC-SECRET-KEY-HERE'

# Use our own Traefik (externally managed)
matrix_playbook_reverse_proxy_type: other-traefik-container
traefik_certs_dumper_enabled: false

postgres_connection_password: 'PASTE-YOUR-POSTGRES-PASSWORD-HERE'
devture_systemd_docker_base_docker_service_name: "docker"
matrix_playbook_docker_installation_enabled: false

# We delegate .well-known from the root domain via Nginx
matrix_static_files_enabled: false
matrix_static_files_container_labels_traefik_enabled: false

coturn_enabled: false

# Traefik integration
matrix_playbook_reverse_proxy_container_network: proxy
matrix_playbook_reverse_proxy_hostname: traefik
traefik_entrypoint_primary: websecure
traefik_certResolver_primary: tls_resolver
matrix_federation_traefik_entrypoint_name: synapse

# --- Core services ---
matrix_synapse_container_labels_traefik_docker_network: proxy

matrix_client_element_container_labels_traefik_docker_network: proxy
matrix_client_element_hostname: "{{ matrix_server_fqn_matrix }}"
matrix_client_element_path_prefix: /element

matrix_admin: '@your-username:your-domain.com'

matrix_ketesa_enabled: true
matrix_ketesa_container_http_host_bind_port: ""
matrix_ketesa_container_labels_traefik_docker_network: proxy

# E-Mail relay (optional)
exim_relay_sender_address: "matrix@your-domain.com"
exim_relay_relay_use: true
exim_relay_relay_host_name: "mail.your-domain.com"
exim_relay_relay_host_port: 587
exim_relay_relay_auth: true
exim_relay_relay_auth_username: "user@your-domain.com"
exim_relay_relay_auth_password: "YOUR-EMAIL-PASSWORD"

matrix_appservice_double_puppet_enabled: true

# --- Bridges ---
# The playbook can install and configure various bridges to connect your Matrix homeserver
# to other chat networks like Telegram, Discord, Signal, etc. When enabling a bridge,
# it is crucial to provide the `homeserver` configuration block to ensure it communicates
# correctly with Synapse via Traefik. For more details on available bridges and their
# specific configurations, refer to the official documentation.

# Telegram – Correct EntryPoints + specify Homeserver
matrix_mautrix_telegram_enabled: true
matrix_mautrix_telegram_api_id: 12345678
matrix_mautrix_telegram_api_hash: 'your_telegram_api_hash'
matrix_mautrix_telegram_configuration_extension_yaml: |
  homeserver:
    address: "https://matrix.your-domain.com"
    domain: "your-domain.com"
  bridge:
    permissions:
      '{{ matrix_admin }}': admin

# Meta Messenger
matrix_mautrix_meta_messenger_enabled: true
matrix_mautrix_meta_messenger_configuration_extension_yaml: |
  homeserver:
    address: "https://matrix.your-domain.com"
    domain: "your-domain.com"
  bridge:
    permissions:
      '*': relay
      'your-domain.com': admin
      '{{ matrix_admin }}': admin

# Discord – Correct Avatar-Proxy & general EntryPoints
matrix_mautrix_discord_enabled: true
matrix_mautrix_discord_configuration_extension_yaml: |
  homeserver:
    address: "https://matrix.your-domain.com"
    domain: "your-domain.com"
ℹ️ TRAEFIK MIDDLEWARES

The playbook generates Traefik router labels for each Matrix component. Do not add custom middleware variables such as *_container_labels_traefik_middlewares; current roles do not use them. If you want to apply security-headers@file or crowdsec-bouncer@docker to Matrix routes, do it via the role-specific *_container_labels_additional_labels variables after checking the generated router names, or enforce the middleware in your Traefik stack.

ℹ️ ELEMENT CALL / MATRIX RTC

Element Call and the Matrix RTC stack are deliberately not enabled in the baseline configuration above. They need additional firewall ports (7881/tcp, 7882/udp, 3479/udp, 5350/tcp, and 30000-30020/udp) and, when using an externally managed Traefik container, explicit TURN/TLS handling. Add them only after the core homeserver is working.

5. Install Ansible and Dependencies

This playbook uses Ansible to automate the setup. Choose the installation method that fits your distribution policy.

For Debian 12 (system pip with –break-system-packages)

# Ensure pip is available
sudo apt install -y python3-pip

# Install Ansible and required Python packages system-wide
sudo pip3 install --break-system-packages ansible docker passlib
# Create and activate a dedicated virtual environment
python3 -m venv ~/.venvs/matrix-ansible
source ~/.venvs/matrix-ansible/bin/activate

# Upgrade pip and install required packages in the venv
pip install --upgrade pip
pip install ansible docker passlib
ℹ️ VIRTUALENV REMINDER

If you use the virtual environment, remember to reactivate it in new shell sessions with: source ~/.venvs/matrix-ansible/bin/activate

ℹ️ USE ONE ANSIBLE ENVIRONMENT

Run ansible-galaxy, ansible-doc, and ansible-playbook with the same Ansible installation. The commands below use sudo because this guide creates /opt/containers/matrix as a root-owned checkout and runs the playbook with sudo. If you use a user-owned checkout or a virtual environment, omit sudo consistently for the Ansible commands.

Next, install the Ansible roles required by the playbook:

cd /opt/containers/matrix
sudo make roles

Install the Docker collection used by several playbook roles:

sudo ansible-galaxy collection install community.docker --upgrade

You can verify that Ansible can resolve the Docker image module:

sudo ansible-doc community.docker.docker_image_pull

6. Configure Ansible Hosts

Now, we need to tell Ansible which server to configure. Since we are running it locally, the setup is simple.

# Copy the example hosts file
sudo cp examples/hosts inventory/hosts

# Edit the file
sudo nano inventory/hosts

Modify the file to look like this. The ansible_connection=local tells Ansible to run all commands on the machine it’s currently on, instead of connecting to a remote server via SSH. Because of this, ansible_host=127.0.0.1 (localhost) is the correct value. Parameters like ansible_ssh_user=root are ignored in this mode, as the playbook runs with the permissions of the user executing it (in our case, root via sudo).

[matrix_servers]
matrix.your-domain.com ansible_host=127.0.0.1 ansible_connection=local

7. Adjust Traefik for Federation

For Matrix federation to work, other servers need to connect to yours on port 8448. We must expose this port in our main Traefik stack and create a dedicated entrypoint for it.

Go to your Traefik stack’s directory (e.g., /opt/containers/traefik-stack).

7.1. Expose Federation Port

Edit your main docker-compose.yml:

sudo nano docker-compose.yml

Add port 8448 to the ports section of the traefik service:

services:
  traefik:
    # ... other settings
    ports:
      - "80:80"
      - "443:443"
      - "8448:8448" # Add this line for Matrix federation
    # ... rest of the settings

7.2. Create Federation and Internal Entrypoints

Edit your static Traefik configuration (traefik/config/traefik.yml):

sudo nano traefik/config/traefik.yml

Add a new synapse entrypoint for federation and a matrix-internal-matrix-client-api entrypoint for internal communication.

For recent playbook versions, it is recommended to also define the internal matrix-internal-matrix-client-api entrypoint on port 8008. This port is not published to the host and serves as an internal C2S route for bridges and add-ons, preventing errors in the Traefik dashboard.

entryPoints:
  web:
    address: ":80"
    # ...
  websecure:
    address: ":443"
  synapse: # For federation
    address: ":8448"
  matrix-internal-matrix-client-api: # For internal C2S communication
    address: ":8008"

7.3. Restart Traefik

Apply the changes by restarting your Traefik stack:

cd /opt/containers/traefik-stack
docker compose up -d

8. Run the Matrix Installation

With all the configuration in place, we can now run the Ansible playbook to set up and install everything.

cd /opt/containers/matrix
sudo ansible-playbook -i inventory/hosts setup.yml --tags=install-all

This command will download all necessary Docker images, generate configuration files, and prepare the services. It may take several minutes. The install-all tag is the recommended path for a new server when you are not importing old Matrix data.

9. Start the Matrix Services

Once the setup is complete, start all the Matrix containers:

sudo ansible-playbook -i inventory/hosts setup.yml --tags=ensure-matrix-users-created,start

After a few moments, all the services you enabled should be running. The ensure-matrix-users-created tag creates the internal service users required by bots and bridges. You can verify the running containers with docker ps.

10. Verify the Installation

After starting the services, it’s crucial to verify that all components are running and communicating correctly.

10.1. Check Running Containers

First, ensure all enabled services are running. The exact names will vary based on your matrix_domain.

docker ps --format '{{.Names}}' | grep -E 'synapse|element|ketesa|mautrix'

10.2. Verify .well-known Delegation (CLI)

Check if your Nginx server is correctly serving the delegation files for your root domain.

# Replace 'your-domain.com' with your actual domain
curl -s https://your-domain.com/.well-known/matrix/server | jq
curl -s https://your-domain.com/.well-known/matrix/client | jq

For this guide, the expected federation delegation uses port 8448:

{
  "m.server": "matrix.your-domain.com:8448"
}

The client delegation should point clients to the Matrix HTTPS endpoint:

{
  "m.homeserver": {
    "base_url": "https://matrix.your-domain.com"
  }
}

10.3. Verify Federation Endpoint (CLI)

Test if the Synapse federation port (8448) is correctly exposed through Traefik.

# Replace 'matrix.your-domain.com' with your actual matrix subdomain
curl -sS https://matrix.your-domain.com:8448/_matrix/federation/v1/version | jq

This should return a JSON object with server information, confirming the federation endpoint is reachable.

10.4. Verify CrowdSec Bouncer Registration

Check if the CrowdSec bouncer for Traefik has successfully registered with the CrowdSec agent.

cd /opt/containers/traefik-stack
docker compose exec crowdsec cscli bouncers list

You should see the Traefik bouncer in the list with a valid status.

10.5. Web-based Federation Test

Finally, use the official Federation Tester for a comprehensive check.

  1. Go to: https://federationtester.matrix.org/
  2. Enter your server name (e.g., your-domain.com) and run the test.

If everything is configured correctly, you should see a success message.

11. Create Your First User

Use the playbook to register your administrator user.

⚠️ PASSWORD SECURITY

To avoid leaving your password in your shell’s history, omit the -e 'password=...' part. The playbook will then prompt you to enter the password securely.

... -e 'username=your-username' -e 'admin=yes'

cd /opt/containers/matrix
sudo ansible-playbook -i inventory/hosts setup.yml --tags=register-user -e 'username=your-username' -e 'admin=yes'

You can re-run this command with admin=no to create non-admin users.

12. Log In with Element

Your homeserver is now ready! You can access the Element web client by navigating to https://matrix.your-domain.com/element.

  1. Click “Sign In”.
  2. Your homeserver should be pre-filled. If not, edit it to show your server’s address (matrix.your-domain.com).
  3. Log in with the username and password you just created.

13. Accessing the Ketesa Admin UI

Because we set matrix_ketesa_enabled: true in our vars.yml configuration, a powerful web-based administration interface for Synapse is automatically deployed.

You can access it at: https://matrix.your-domain.com/synapse-admin

By default, the playbook exposes Ketesa at the /synapse-admin path. If you prefer a Ketesa-branded path, add matrix_ketesa_path_prefix: /ketesa to vars.yml and use https://matrix.your-domain.com/ketesa instead.

Log in with your Matrix administrator account. From here, you can:

  • Manage users (deactivate, make admin, etc.).
  • View server statistics and metrics.
  • Explore rooms and manage their settings.

This interface is the primary tool for the day-to-day administration of your homeserver.

14. Maintenance

Routine Updates

To update your Matrix server and its components to the latest version:

cd /opt/containers/matrix

# Pull the latest changes from the git repository.
sudo git pull

# Update the playbook roles.
sudo make roles

# Keep Ansible collections current.
sudo ansible-galaxy collection install community.docker --upgrade

# It's a good practice to review the CHANGELOG.md for breaking changes.
# sudo nano CHANGELOG.md

# Re-run the install and start tags to apply updates.
sudo ansible-playbook -i inventory/hosts setup.yml --tags=install-all,start

If you removed components from vars.yml, run a full setup instead so the playbook can uninstall disabled services:

sudo ansible-playbook -i inventory/hosts setup.yml --tags=setup-all,ensure-matrix-users-created,start

Migration: Preserve Homeserver Identity

For a fresh homeserver, the normal installation flow above is enough. For a server migration where existing users should keep the same Matrix ID, clients should remain logged in, and bridges should continue to work, copying only vars.yml is not enough.

Preserve at least:

  • The Synapse database.
  • The Synapse signing key.
  • Appservice registration files and tokens.
  • Bridge configuration and bridge databases/state.
  • The same matrix_domain and public .well-known delegation.
  • Media that you still care about, especially local profile and room avatars.
⚠️ DO NOT RUN BRIDGES TWICE

When migrating bridge-heavy setups, stop the old bridge services before starting the new ones. Running the same Signal, Telegram, Discord, Instagram, Messenger, or LinkedIn bridge state on two servers at the same time can cause external-service conflicts and duplicate sessions.

If historical Matrix media is not important, you can keep the migration lightweight by skipping most remote media. Do not forget that avatars are also media. A useful compromise is:

  • Keep local media required for account and room avatars.
  • Allow old remote cached media to be refetched when possible.
  • Enable remote media retention so cached remote media does not grow forever.

In Synapse’s media retention config, the intended policy looks like:

media_retention:
  local_media_lifetime: null
  remote_media_lifetime: 14d

This keeps local uploads while allowing Synapse to purge remote cached media that has not been accessed recently.

Upgrade Notes for Existing Installations

Fresh installations using the configuration above can skip this subsection. These notes are for servers that were originally deployed with older versions of the playbook.

Playbook migration validation

Older installations can be blocked by the playbook’s migration validation gate:

Your validated migration version (v2026.03.23.0) is behind the expected version (v2026.05.18.0).

This is an intentional safety check. Review the relevant playbook changelog entries first, then raise the validated version in your host-specific vars.yml:

matrix_playbook_migration_validated_version: "v2026.05.18.0"

The file usually lives at:

inventory/host_vars/matrix.your-domain.com/vars.yml

Ketesa configuration for older Synapse Admin setups

The bundled administration UI is now configured with matrix_ketesa_* variables. Existing configurations that still contain matrix_synapse_admin_* variables should be updated:

matrix_ketesa_enabled: true
matrix_ketesa_container_http_host_bind_port: ""
matrix_ketesa_container_labels_traefik_docker_network: proxy
matrix_ketesa_container_labels_traefik_entrypoints: websecure
matrix_ketesa_container_labels_traefik_tls_certResolver: tls_resolver

You can check your configuration with:

grep -nE 'matrix_synapse_admin_|matrix_ketesa_' \
inventory/host_vars/matrix.your-domain.com/vars.yml

If any matrix_synapse_admin_* variables still appear, rename them to their matrix_ketesa_* equivalents.

Keep the Docker collection current

The setup section already installs community.docker. On older installations, update it before running the playbook:

sudo ansible-galaxy collection install community.docker --upgrade

This avoids errors like:

ERROR! couldn't resolve module/action 'community.docker.docker_image_pull'.

Verify that Ansible can resolve the module:

sudo ansible-doc community.docker.docker_image_pull

make roles installs Ansible roles, but it does not necessarily install or upgrade collections. If you see a warning that community.docker does not support your Ansible version and later tasks fail, update Ansible itself, preferably in the dedicated Python virtual environment described above.

Remove the obsolete Telegram public endpoint variable

The Go/bridgev2-based mautrix-telegram bridge no longer has a public web-based login endpoint. Existing configurations must not keep this variable:

matrix_mautrix_telegram_container_labels_public_endpoint_traefik_entrypoints -> <removed (the bridge no longer has a public web-based login endpoint)>

Remove or comment out this line:

# matrix_mautrix_telegram_container_labels_public_endpoint_traefik_entrypoints: websecure

After applying the relevant upgrade notes, run the update again:

sudo ansible-playbook -i inventory/hosts setup.yml --tags=install-all,start

Upgrading PostgreSQL

If the playbook notifies you about a new PostgreSQL version being available, you can perform the upgrade with a specific tag.

⚠️ BACKUP RECOMMENDED

Before upgrading the database, ensure you have a recent backup of your data.

cd /opt/containers/matrix
sudo ansible-playbook -i inventory/hosts setup.yml --tags=upgrade-postgres

Stopping and Uninstalling

To stop all Matrix services:

cd /opt/containers/matrix
sudo ansible-playbook -i inventory/hosts setup.yml --tags=stop

To completely uninstall and delete all data:

cd /opt/containers/matrix
sudo ansible-playbook -i inventory/hosts setup.yml --tags=uninstall
⚠️ DATA LOSS

The uninstall tag is destructive and will remove all user data, chat history, and media. Use with extreme caution.

15. Troubleshooting

Below are common issues and quick checks to resolve them.

⚠️ TIP: VERIFY ONE LAYER AT A TIME

When in doubt, verify DNS → Traefik → Nginx .well-known → Synapse federation endpoint step by step. Isolate the layer that fails.

15.1 Federation test fails

  • Ensure port 8448 is exposed by Traefik and the synapse entrypoint exists.
cd /opt/containers/traefik-stack
docker compose ps traefik
docker inspect traefik | jq '.[0].NetworkSettings.Ports["8448/tcp"]'
  • Test the federation version endpoint directly:
curl -sS https://matrix.your-domain.com:8448/_matrix/federation/v1/version | jq

15.2 .well-known is invalid or missing

  • Verify the .well-known delegation from your root domain (served by Nginx server):
curl -s https://your-domain.com/.well-known/matrix/server | jq
curl -s https://your-domain.com/.well-known/matrix/client | jq
  • For this Traefik setup, /.well-known/matrix/server must return "m.server": "matrix.your-domain.com:8448". A value ending in :443 only works if your federation router is intentionally exposed on port 443.
  • Re-check that the placeholders in conf/nginx.conf were replaced via sed and that CORS headers middleware uses camelCase keys in Traefik labels (e.g., accessControlAllowOriginList).

15.3 Traefik/CrowdSec middleware not applied

  • Ensure you reference providers correctly:
    • Security headers: security-headers@file
    • CrowdSec bouncer: crowdsec-bouncer@docker

15.4 ACME/TLS issues

  • Inspect Traefik logs for ACME errors and DNS problems:
docker compose logs -n 200 traefik | tail -n 200
  • Confirm A/AAAA records for traefik.your-domain.com and matrix.your-domain.com point to your server and that port 443 is reachable.
  • Disable CDN Proxy for Initial Certificate: If you are using a CDN like Cloudflare, ensure the proxy is disabled (set to “DNS Only” or “grey cloud”) for your domains during the first certificate request. The HTTP-01 challenge requires Let’s Encrypt to reach your server directly. You can re-enable the proxy after the certificate is issued.

15.5 Docker network not found

  • Make sure the external proxy network exists and is used by all services behind Traefik:
docker network ls | grep proxy || docker network create proxy

15.6 Permission problems in inventory/host_vars

  • If editing files is cumbersome, you can align ownership (optional):
sudo chown -R "$USER":"$USER" /opt/containers/matrix/inventory

15.7 Ansible/venv not active

  • If you installed Ansible in a virtualenv, reactivate it before running playbooks:
source ~/.venvs/matrix-ansible/bin/activate

15.8 CrowdSec bouncer not registered

  • Check the bouncer status inside the CrowdSec container (run in your Traefik stack directory):
docker compose exec crowdsec cscli bouncers list

15.9 Element Call: MISSING_MATRIX_RTC_TRANSPORT

If Element Web or another client says the server is not configured for Element Call and reports MISSING_MATRIX_RTC_TRANSPORT, the client did not discover your MatrixRTC/LiveKit transport.

When using Element Call with LiveKit, the root-domain /.well-known/matrix/client response must include the RTC focus information in addition to the homeserver delegation:

{
  "m.homeserver": {
    "base_url": "https://matrix.your-domain.com"
  },
  "org.matrix.msc4143.rtc_foci": [
    {
      "type": "livekit",
      "livekit_service_url": "https://matrix.your-domain.com/livekit-jwt-service"
    }
  ]
}

If the root domain serves .well-known from an Nginx container with a single-file bind mount, replacing the file on the host may leave the running container attached to the old inode. After editing the well-known response, recreate the Nginx container or edit the mounted file in place and reload Nginx.

Conclusion

You now have a fully functional, federated Matrix Synapse homeserver integrated with your Traefik proxy and Nginx web server. This powerful setup not only gives you control over your own secure communications but is also extensible with numerous bridges and features, allowing you to create a central hub for all your chat services.

📚PLAYBOOK DOCUMENTATION 🛡️MATRIX DOCUMENTATION