Deploying a Matrix Synapse Server with Docker and Traefik

| Federated chat under an orange evening sky. |
Table of Contents 📑
- Changelog
- 1. Prerequisites
- 2. Directory Structure
- 3. Download the Ansible Playbook
- 4. Initial Configuration
- 5. Install Ansible and Dependencies
- 6. Configure Ansible Hosts
- 7. Adjust Traefik for Federation
- 8. Run the Matrix Installation
- 9. Start the Matrix Services
- 10. Verify the Installation
- 11. Create Your First User
- 12. Log In with Element
- 13. Accessing the Ketesa Admin UI
- 14. Maintenance
- 15. Troubleshooting
- 15.1 Federation test fails
- 15.2 .well-known is invalid or missing
- 15.3 Traefik/CrowdSec middleware not applied
- 15.4 ACME/TLS issues
- 15.5 Docker network not found
- 15.6 Permission problems in inventory/host_vars
- 15.7 Ansible/venv not active
- 15.8 CrowdSec bouncer not registered
- 15.9 Element Call: MISSING_MATRIX_RTC_TRANSPORT
- Conclusion
- Sources
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
| Date | Change |
|---|---|
| 2026-10-11 | Fixed middleware, Python isolation and bridge variable migrations; clarified RTC security upgrades, federation/OpenID discovery, homeserver access boundaries, encrypted-call acceptance and Docker boot dependencies. |
| 2026-07-17 | Authentication Guide: Linked the safe Authelia OIDC and 2FA migration for existing Synapse accounts. |
| 2026-07-01 | Migration Notes: Added homeserver identity, bridge-state, MatrixRTC discovery, and media-retention guidance. |
| 2026-06-12 | Playbook Compatibility: Refreshed the guide for current Ketesa, Telegram bridgev2, migration validation, Docker collection requirements, and a safer baseline without Matrix RTC. |
| 2026-02-20 | Configuration Update: Updated matrix_coturn_enabled to coturn_enabled, removed Sliding Sync, and added PostgreSQL upgrade instructions. |
| 2025-09-17 | Initial Version: Guide created, focusing on Traefik v3 integration, Ansible deployment, and enabling various bridges and features like Sliding Sync. |
1. Prerequisites
- Traefik v3 and CrowdSec with Docker Compose: A Modern Security Stack: This is the foundation for our reverse proxy and security.
- 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/matrixdelegation required for federation.
| ⚠️ HARD REQUIREMENT |
|
| ℹ️ ROOT DOMAIN REMAINS FREE FOR YOUR WEBSITE |
Synapse runs on a dedicated subdomain (e.g., |
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)
│
▼
SynapseYou will also need:
git,jq, andpwgenfor version control, JSON parsing, and password generation.- Python 3 and
pipinstalled on your server. sudoor root access.
Install the required tools:
sudo apt update && sudo apt -y install git jq pwgen python3-pip python3-venv make2. Directory Structure
First, we’ll create a dedicated directory for the Matrix playbook configuration and files.
sudo mkdir -p /opt/containers/matrix3. 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/matrix4. 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 |
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 14.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.ymlBelow 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.service"
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_bridge_mautrix_telegram_enabled: true
matrix_bridge_mautrix_telegram_api_id: 12345678
matrix_bridge_mautrix_telegram_api_hash: 'your_telegram_api_hash'
matrix_bridge_mautrix_telegram_configuration_extension_yaml: |
homeserver:
address: "https://matrix.your-domain.com"
domain: "your-domain.com"
bridge:
permissions:
'{{ matrix_admin }}': admin
# Meta Messenger
matrix_bridge_mautrix_meta_messenger_enabled: true
matrix_bridge_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_bridge_mautrix_discord_enabled: true
matrix_bridge_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 |
| ℹ️ 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 ( |
5. Install Ansible and Dependencies
This playbook uses Ansible to automate the setup. Choose the installation method that fits your distribution policy.
Use a dedicated, root-owned Python environment
Keep Ansible separate from distribution-managed Python packages. On Debian 12/13, install the virtual-environment support, then create an administrative environment whose executables cannot be modified by unprivileged accounts:
sudo apt install -y python3-venv
sudo python3 -m venv /opt/ansible-venvs/matrix
sudo /opt/ansible-venvs/matrix/bin/pip install --upgrade pip
sudo /opt/ansible-venvs/matrix/bin/pip install ansible docker passlibUse the same environment for role installation, collections and playbook runs; review the checked-out playbook’s supported Ansible versions before upgrading it. Do not use --break-system-packages on the host. Python virtual environments, Debian externally managed Python.
Next, install the Ansible roles required by the playbook:
cd /opt/containers/matrix
sudo env PATH=/opt/ansible-venvs/matrix/bin:/usr/bin:/bin make rolesInstall the Docker collection used by several playbook roles:
sudo /opt/ansible-venvs/matrix/bin/ansible-galaxy collection install community.docker --upgradeYou can verify that Ansible can resolve the Docker image module:
sudo ansible-doc community.docker.docker_image_pull6. 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/hostsModify 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=local7. 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.ymlAdd 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 settings7.2. Create Federation and Internal Entrypoints
Edit your static Traefik configuration (traefik/config/traefik.yml):
sudo nano traefik/config/traefik.ymlAdd 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 -d8. 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 /opt/ansible-venvs/matrix/bin/ansible-playbook -i inventory/hosts setup.yml --tags=install-allThis 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 /opt/ansible-venvs/matrix/bin/ansible-playbook -i inventory/hosts setup.yml --tags=ensure-matrix-users-created,startAfter 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 | jqFor 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 | jqThis 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 listYou 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.
- Go to: https://federationtester.matrix.org/
- 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
|
cd /opt/containers/matrix
sudo /opt/ansible-venvs/matrix/bin/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.
- Click “Sign In”.
- Your homeserver should be pre-filled. If not, edit it to show your server’s address (
matrix.your-domain.com). - 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 env PATH=/opt/ansible-venvs/matrix/bin:/usr/bin:/bin make roles
# Keep Ansible collections current.
sudo /opt/ansible-venvs/matrix/bin/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 /opt/ansible-venvs/matrix/bin/ansible-playbook -i inventory/hosts setup.yml --tags=install-all,startIf you removed components from vars.yml, run a full setup instead so the playbook can uninstall disabled services:
sudo /opt/ansible-venvs/matrix/bin/ansible-playbook -i inventory/hosts setup.yml --tags=setup-all,ensure-matrix-users-created,startRTC updates and authenticated media publishing
Update Element Call, the LiveKit JWT service and LiveKit Server as a compatible set. JWT service 0.8.0 fixes OpenID-response validation in legacy token endpoints and changes request parameters; updating only one RTC component can break clients. Keep trusted homeserver settings explicit. Adding a homeserver here permits media publishing and is a policy change, not a cosmetic compatibility workaround. JWT service security/update notes, playbook RTC documentation.
The delegated federation address in /.well-known/matrix/server must reach a listener serving the OpenID user-info API. In this guide that is matrix.your-domain.com:8448, not the client-only HTTPS listener on port 443. Successful client login and JWT-service health checks can coexist with failed call authentication when delegation reaches the wrong listener. Verify the configured delegation and the OpenID route, and account for the JWT service’s one-hour federation-discovery cache when correcting it. Synapse listener resources, JWT service discovery and OpenID implementation.
For the deprecated OpenID-based /sfu/get and /get_token endpoints, set LIVEKIT_FULL_ACCESS_HOMESERVERS to the exact Matrix server names you operate, such as your-domain.com; avoid * for a private SFU. Full access allows media publishing and room creation. A valid user from another homeserver receives restricted access instead. Do not confuse this setting with an OIDC issuer URL or the hostname of the reverse proxy. The OpenID response must also identify a user belonging to the homeserver that answered it. The OpenID-sub advisory requires the Rust 0.8.0 fix; Go 0.5.0 fixed the earlier missing full-access configuration default, which is a separate issue.
Check the configured client URLs and actual request paths when moving to the newer Client-Server RTC endpoints. Discovery pointing at a LiveKit service does not establish that every client has stopped using legacy endpoints. In application-service mode, the newer endpoints use their own homeserver authentication rather than the legacy full-access list; validate the application-service registration and proxy boundaries as part of that migration. Official endpoint and application-service configuration.
After an RTC update, test a regular encrypted call between two authorized clients in a test room, including token acquisition, bidirectional audio/video, and a reconnect. A /healthz response or a synthetic signed-token test proves only part of this path. Check rejection of a mismatched OpenID subject and restricted grants for an untrusted homeserver in an isolated mock deployment using its own credentials; do not impersonate production users or attach that test to the production SFU. Keep the full encrypted-call acceptance open until it has actually been performed.
For external Traefik, TURN over TLS may need a dedicated TLS option allowing stun.turn and stun.nat-discovery as well as the reviewed HTTP/ACME protocols. Bind that option to the TURN router only and test clients advertising ALPN. Playbook TURN/TLS configuration.
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_domainand public.well-knowndelegation. - 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: 14dThis 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.ymlKetesa 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_resolverYou can check your configuration with:
grep -nE 'matrix_synapse_admin_|matrix_ketesa_' \
inventory/host_vars/matrix.your-domain.com/vars.ymlIf 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 /opt/ansible-venvs/matrix/bin/ansible-galaxy collection install community.docker --upgradeThis 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_pullmake 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_bridge_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_bridge_mautrix_telegram_container_labels_public_endpoint_traefik_entrypoints: websecureAfter applying the relevant upgrade notes, run the update again:
sudo /opt/ansible-venvs/matrix/bin/ansible-playbook -i inventory/hosts setup.yml --tags=install-all,startUpgrading 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 /opt/ansible-venvs/matrix/bin/ansible-playbook -i inventory/hosts setup.yml --tags=upgrade-postgresStopping and Uninstalling
To stop all Matrix services:
cd /opt/containers/matrix
sudo /opt/ansible-venvs/matrix/bin/ansible-playbook -i inventory/hosts setup.yml --tags=stopTo completely uninstall and delete all data:
cd /opt/containers/matrix
sudo /opt/ansible-venvs/matrix/bin/ansible-playbook -i inventory/hosts setup.yml --tags=uninstall| ⚠️ DATA LOSS |
The |
Existing bridge configuration after playbook updates
Current playbook roles use matrix_bridge_* variable prefixes. Rename older matrix_mautrix_* and matrix_beeper_* settings using the official migration map before deployment. This changes Ansible variable names; existing service names, database identities, registration tokens and /matrix data paths must remain intact. Review the migration log and validate the generated configuration before stopping live bridges. Official bridge rename map.
The archived Beeper LinkedIn bridge has no maintained update path. Moving to mautrix-linkedin requires a separately reviewed migration and browser authentication; do not enable both bridges against the same appservice namespaces. Playbook LinkedIn bridge guidance.
Verify service ordering before a host reboot
Successful installation does not prove that every Matrix service starts after a reboot. When Docker is managed outside the playbook, check the generated systemd dependencies, especially for PostgreSQL and Synapse:
systemctl show matrix-postgres.service matrix-synapse.service -p Requires -p AfterThe PostgreSQL unit must wait for docker.service; Synapse must wait for PostgreSQL. If the Docker dependency is missing, correct the playbook configuration and inspect the regenerated units. A separate systemd drop-in can also preserve the dependency for affected Docker-backed Matrix units:
[Unit]
Requires=docker.service
After=docker.service network-online.target
Wants=network-online.targetUse systemctl edit for each affected unit, reload systemd, and start dependent services through their systemd units. These units recreate containers; docker start cannot recover a container that the unit removed during shutdown. After a planned reboot, check all enabled Matrix services and the public client/federation endpoints before considering the update complete.
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 |
15.1 Federation test fails
- Ensure port 8448 is exposed by Traefik and the
synapseentrypoint 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 | jq15.2 .well-known is invalid or missing
- Verify the
.well-knowndelegation 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/servermust return"m.server": "matrix.your-domain.com:8448". A value ending in:443only works if your federation router is intentionally exposed on port 443. - Re-check that the placeholders in
conf/nginx.confwere replaced viasedand 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@file
- Security headers:
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.comandmatrix.your-domain.compoint 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
proxynetwork exists and is used by all services behind Traefik:
docker network ls | grep proxy || docker network create proxy15.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/inventory15.7 Ansible/venv not active
- If you installed Ansible in a virtualenv, reactivate it before running playbooks:
source ~/.venvs/matrix-ansible/bin/activate15.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 list15.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.
If you want to require TOTP or WebAuthn for compatible human SSO logins, continue with Adding Authelia 2FA to Matrix Synapse with OpenID Connect. Read its Element X compatibility gate and existing-account migration checks before disabling local passwords.
Sources
- JWT service 0.8.0 release and breaking changes
- OpenID subject validation security advisory
- Full-access homeserver configuration security advisory
- Official JWT service configuration and endpoint documentation
- Playbook MatrixRTC configuration
- Playbook LiveKit TURN/TLS configuration





