Introduction
If you want to host your own secure, decentralized chat server using the Matrix protocol, this guide will help you get started. We’ll walk through setting up a Synapse server on a Synology NAS using Docker or Container Manager (DSM 7.2+). This tutorial is written for technically interested beginners with some experience using terminals and configuring home networks.
Requirements
You’ll need a Synology NAS with an x86 CPU (Intel or AMD). ARM-based NAS models, often found in J-series devices, are not supported. Your system should be running DSM 7.2 or newer with Container Manager installed. Make sure you know how to forward ports on your router and access your NAS terminal. You’ll also need a (sub-)domain and access to your DNS settings for routing and SSL configuration.
Preparing the Installation
First, install Container Manager from the Synology Package Center. Open File Station and go to the docker
folder. Inside this folder, create a new directory named synapse
. Within the synapse
directory, create two subfolders: data
and db
. These folders will be used for volume mounting during container creation.
Server Name and Delegation
Before proceeding to generate the Synapse configuration files, it’s crucial to understand the concept of delegation in Matrix and how it affects the server name you choose. This decision cannot be easily changed later, so it’s important to get it right from the start.
In Matrix, the server name (also referred to as the server’s domain name) is a key identifier. It becomes part of your Matrix ID (e.g., @user:yourdomain.com) and is also used when other servers federate with yours. Therefore, selecting the appropriate domain name is essential.
By default, your Matrix server must be accessible directly via the domain you specify as the server name — specifically over port 8448. This is the default port the Matrix federation protocol uses for communication between servers. But depending on your setup (especially when using Docker, reverse proxies, or non-root domains), this might not be ideal.
What is Delegation?
Delegation allows you to separate your public Matrix domain from the actual server hosting Synapse. It works by placing a small .well-known file at https://example.com/.well-known/matrix/server, which tells the rest of the Matrix network where your Synapse server is really hosted.
Example .well-known/matrix/server:
{ "m.server": "matrix.example.com:443" }
With this setup, other servers will federate with matrix.example.com (on port 443), but your users will still appear as @user:example.com.
This is useful if you:
- Want clean Matrix IDs based on your main domain.
- Can’t or don’t want to expose Synapse directly on port 8448.
- Run multiple services and want to keep Matrix on a subdomain.
Why Delegation Matters Now
Deciding whether or not to use delegation impacts what you enter as your server name in the Synapse configuration. If you plan to use delegation and set up a .well-known file, you can safely use your root or top level domain (e.g., example.com) as the server name, even if your actual Synapse instance is running on a subdomain (e.g., matrix.example.com).
Note: To setup a .well-known file you must be able to access the respective webserver of your top level domain service (e.g. webserver of example.com)
However, if you’re not planning to use delegation, the server name must exactly match the hostname where Synapse will be accessible on port 8448 over the internet.
Note: If you can only expose port 443 there are some tricks to be able to enable federation nevertheless. However I will not go into the details how to setup these kind of deployments to avoid confusing people.
Anyways make sure to decide this now, before moving on to generate the Synapse config files, as the server name you set during configuration will become a permanent and public part of your server’s identity in the Matrix network.
Generating the Configuration File
We’ll now generate the initial Synapse configuration using Docker. Open the Control Panel, go to Task Scheduler, and create a new user-defined script running as root. Use the following script (update UID, GID, paths, and domain as needed):
#!/bin/bash docker run --rm \ --user 1026:100 \ -v /volume1/docker/synapse/data:/data \ -e SYNAPSE_CONFIG_PATH=/data/homeserver.yaml \ -e SYNAPSE_SERVER_NAME=yourdomain.com \ -e SYNAPSE_REPORT_STATS=yes \ matrixdotorg/synapse:latest generate
Save and run the script once. It will generate the homeserver.yaml
configuration file inside /volume1/docker/synapse/data
.
Editing the Configuration File
Navigate to /volume1/docker/synapse/data
and open homeserver.yaml
in a text editor. Add the following lines under your server name:
enable_registration: true enable_registration_without_verification: true enable_group_creation: true
Now remove the default SQLite database configuration within the homeserver.yaml:
database: name: sqlite3 args: database: /data/homeserver.db
Replace it with a PostgreSQL configuration:
database: name: psycopg2 args: user: synapseuser password: synapsepass database: synapsedb host: synapse-db cp_min: 5 cp_max: 10
Modify user
, password
, and database
according to your preferences. Save the file.
Creating the Docker Compose File
Open Container Manager, go to Projects, and create a new project. Set the project name (for example: matrix
) and select the synapse
directory as the project path. Choose to create the docker-compose.yml
manually, then enter the following configuration:
version: "3.9" services: synapse-db: image: postgres:17.4 container_name: Synapse-DB hostname: synapse-db security_opt: - no-new-privileges:true healthcheck: test: ["CMD", "pg_isready", "-q", "-d", "synapsedb", "-U", "synapseuser"] timeout: 45s interval: 10s retries: 10 user: 1026:100 volumes: - /volume1/docker/synapse/db:/var/lib/postgresql/data environment: - POSTGRES_DB=synapsedb - POSTGRES_USER=synapseuser - POSTGRES_PASSWORD=synapsepass - POSTGRES_INITDB_ARGS=--encoding=UTF-8 --lc-collate=C --lc-ctype=C restart: always synapse: image: matrixdotorg/synapse:latest container_name: Synapse hostname: synapse security_opt: - no-new-privileges:true user: 1026:100 environment: - TZ=Europe/Berlin - SYNAPSE_CONFIG_PATH=/data/homeserver.yaml volumes: - /volume1/docker/synapse/data:/data ports: - 8008:8008 restart: always depends_on: synapse-db: condition: service_started
Adjust the values for volume paths, user ID, and database credentials to match your setup. Then start the project.
After starting, you should see two running containers in Container Manager: Synapse
and Synapse-DB
.
Setting Up Domain and Reverse Proxy
To make your Matrix server accessible from the internet, you need to set up a (sub-)domain and a reverse proxy.
First, configure your DNS provider to point your domain or subdomain to your NAS IP address. If you have a dynamic IP, use Dynamic DNS or set a CNAME record pointing to an existing DDNS entry.
Open Synology Control Panel and go to Login Portal. Under the Advanced tab, open the Reverse Proxy settings and create a new entry.
Under the General tab:
Source: Protocol: HTTPS Hostname: your.sub.domain Port: 443 Enable HSTS. Destination: Protocol: HTTP Hostname: localhost Port: 8008 Switch to the Custom Header tab and add the following: Header name: Upgrade Value: $http_upgrade Header name: Connection Value: $connection_upgrade
Save the entry.
Now go to Control Panel > Security > Certificate. Create a new Let’s Encrypt certificate for your subdomain. After creation, assign this certificate to your reverse proxy rule under the Settings tab.
Once everything is set up, test your domain in a browser. You should be forwarded to your Synapse server running on port 8008 and served over HTTPS with a valid SSL certificate.
Registering a New User
To add a new user to your Matrix server, open your terminal or SSH into your NAS and run the following:
cd /volume1/docker/synapse docker-compose exec synapse register_new_matrix_user -c /data/homeserver.yaml http://localhost:8008
Allow the prompts to enter a username and password. Your Matrix ID will be in the format @username:yourdomain.com
.
Optimizing Homeserver Configuration
After your homeserver.yaml file has been generated, there are a few key tweaks and optimizations you can make to improve your Synapse server’s performance, security, and user experience.
Here are some recommended adjustments:
Federation & Client Management
Federation is enabled by default, but you can fine-tune federation behavior and client access:
serve_server_wellknown: false # only relevant if you want to delegated federation traffic over port 443 insteadt of 8448 allow_profile_lookup_over_federation: true # Show profile information to other homeservsers allow_device_name_lookup_over_federation: false # Show Device Usage to other homeservers allow_public_rooms_over_federation: false # Show Room Directory to other homeservers federation: client_timeout: 180s max_short_retry_delay: 7s max_long_retry_delay: 100s max_short_retries: 5 max_long_retries: 20 destination_min_retry_interval: 30s destination_retry_multiplier: 5 destination_max_retry_interval: 12h
Element Call (Voice/Video)
To be able to use the new MatrixRTC backend:
experimental_features: msc3266_enabled: true msc4222_enabled: true max_event_delay_duration: 24h rc_message: per_second: 0.5 burst_count: 30 rc_delayed_event_mgmt: per_second: 1 burst_count: 20
Deletion Policy
Define a data retention policy to limit the size of your database and comply with privacy standards:
retention: enabled: true default_policy: min_lifetime: 1d max_lifetime: 728d allowed_lifetime_min: 1d allowed_lifetime_max: 728d purge_jobs: - interval: 1d
Files and Media Usage
Manage storage growth and behavior:
media_store_path: /data/media_store max_upload_size: "200M" max_image_pixels: "35M" dynamic_thumbnails: false enable_media_repo: true media_retention: local_media_lifetime: 728d remote_media_lifetime: 90d
You can also run a media cleanup cronjob periodically.
Thumbnail Settings
thumbnail_sizes: - width: 32 height: 32 method: crop - width: 96 height: 96 method: crop - width: 320 height: 240 method: scale - width: 640 height: 480 method: scale - width: 800 height: 600 method: scale
User Registration
Control who can register and how:
enable_registration: true registration_requires_token: true # If true one has to enter a registration token (see value below) to succesfull register to the homeserver token: <define> allow_guest_access: false enable_registration_without_verification: true registration_shared_secret: "<DEFINE>" auto_join_rooms: # Define rooms a new user auto joins - "#Lobby:example.com" registrations_require_3pid: # Requires E-Mail to register - email enable_3pid_lookup: true # E-MAil confirmation required to register bcrypt_rounds: 12
Caching
Reduce memory usage on low-resource devices like a Synology NAS:
event_cache_size: 20K caches: global_factor: 1.0 per_cache_factors: get_users_who_share_room_with_user: 2.0 sync_response_cache_duration: 2m cache_autotuning: max_cache_memory_usage: 2048M target_cache_memory_usage: 1024M min_cache_ttl: 5m
Web Client Delegation
If you don’t host your own Element Web, delegate to a trusted public client. Otherwise define your Element Web client domain:
web_client_location: "https://app.element.io"
Email Configuration
Set up email to enable password resets and notifications:
email: smtp_host: mail.example.com smtp_port: 587 smtp_user: youruser@example.com smtp_pass: yourpassword notif_from: "Matrix Server <noreply@example.com>" require_transport_security: true
Misc
The default homeserver.yaml is huge. Once you’ve set your desired options, it’s a good idea to comment out or remove unused keys to make your config easier to maintain. More information on the available config parameter and best practise settings can be found here.
Conclusion
You’ve now set up your own self-hosted Matrix Synapse server on a Synology NAS with Docker, PostgreSQL, SSL encryption, domain routing and an optimized homeserver config. You can now log in using any Matrix-compatible client like Element and start chatting securely.