Deploying a Synapse Homeserver with Docker

Synapse Configuration

  1. Synapse Configuration
  2. Default File
  3. Log Config
  4. Homeserver Config
  5. Cache Optimisation

Default File

Before we can modify the Synapse config, we need to create it.

Run this command to launch Synapse only to generate the config file and then close again:

docker compose run -it synapse generate && docker compose down -v

In your "synapse" directory you should now find a number of files like this:

/synapse# ls -lh
total 16K
-rw-r--r-- 1 root root  694 Dec 20 23:20 mydomain.com.log.config
-rw-r--r-- 1 root root   59 Dec 20 23:20 mydomain.com.signing.key
-rw-r--r-- 1 root root 1.3K Dec 20 23:20 homeserver.yaml

The signing key is unique to your server and is vital to maintain for other servers to trust yours in the future. You can wipe the entire database and still be able to federate with other servers if your signing key is the same, so it's worthwhile backing this up now.

Log Config

For the log config, by default this is very barebones and just logs straight to console, but you could replace it with something like this to keep a daily log for the past 3 days in your "logs" folder:

version: 1
formatters:
  precise:
    format: '%(asctime)s - %(name)s - %(lineno)d - %(levelname)s - %(request)s - %(message)s'

handlers:
  file:
    class: logging.handlers.TimedRotatingFileHandler
    formatter: precise
    filename: /logs/synapse.log
    when: midnight
    backupCount: 3
    encoding: utf8

  buffer:
    class: synapse.logging.handlers.PeriodicallyFlushingMemoryHandler
    target: file
    capacity: 10
    flushLevel: 30
    period: 5

loggers:
    synapse:
        level: INFO
        handlers: [buffer]
    synapse.storage.SQL:
        level: INFO
        handlers: [buffer]
    shared_secret_authenticator:
        level: INFO
        handlers: [buffer]

root:
    level: INFO
    handlers: [buffer]

Homeserver Config

By default, this file is quite short and relies a lot on defaults. There is no harm adding blank lines between entries here to make it more readable, or adding comments (starting with the # hash character) to explain what lines mean.

Note: The "secret" or "key" lines are unique to your server and things are likely to misbehave if you change some of them after the server is running. It's generally best to leave them safe at the bottom of the file while you work on the other values.

Here's an example with comments you may wish to use to start with some safe defaults:

# Basic Server Details
server_name: "mydomain.com" # Domain name used by other homeservers to connect to you
public_baseurl: "https://matrix.mydomain.com/" # Public URL of your Synapse server
admin_contact: "mailto:admin@mydomain.com" # Contact email for the server admin
pid_file: "/data/process.pid" # File that stores the process ID of the Synapse server
signing_key_path: "/data/mydomain.com.signing.key" # Location of the signing key for the server

# Logging and Monitoring
log_config: "/data/log.config/synapse.log.config" # Path to the logging configuration file
report_stats: false # Whether to report anonymous statistics
enable_metrics: false # Enable the metrics listener to monitor with Prometheus

# Login and Registration
enable_registration: false # Whether to allow users to register on this server
enable_registration_captcha: true # Whether to enable CAPTCHA for registration
enable_registration_without_verification: false # Allow users to register without email verification
delete_stale_devices_after: 30d # Devices not synced in this long will have their tokens and pushers retired
password_config:
  enabled: true # Set to false to only allow SSO login

# Database and Storage Configuration
database:
  name: psycopg2 # PostgreSQL adapter for Python
  args:
    user: synapse # Username to login to Postgres
    password: SuperSecretPassword # Password for Postgres
    database: synapse # Name of the database in Postgres
    host: "/sockets" # Hostname of the Postgres server, or socket directory
    cp_min: 1 # Minimum number of database connections to keep open
    cp_max: 20 # Maximum number of database connections to keep open

# Redis Configuration
redis:
  enabled: true # Required for workers to operate correctly
  path: "/sockets/synapse_redis.sock" # Path to Redis listening socket

# Network Configuration
listeners:
  - path: "/sockets/synapse_replication_main.sock" # Path to Unix socket
    type: http # Type of listener, almost always http
    resources:
      - names: [replication] # Replication allows workers to communicate with the main thread
        compress: false # Whether to compress responses
  - path: "/sockets/synapse_inbound_main.sock" # Path to Unix socket
    type: http # Type of listener, almost always http
    x_forwarded: true # Use the 'X-Forwarded-For' header to recognise the client IP address
    resources:
      - names: [client, federation] # Client API and federation between homeservers
        compress: false # Whether to compress responses
  - type: metrics # Used for Prometheus metrics later
    port: 10101 # Easy port to remember later?

# Workers will eventually go here
instance_map:
  main: # The main process should always be here
    path: "/sockets/synapse_replication_main.sock"

# Trusted Key Servers
trusted_key_servers: # Servers to check for server keys when another server's keys are unknown
  - server_name: "beeper.com"
  - server_name: "matrix.org"
  - server_name: "t2bot.io"
suppress_key_server_warning: true # Suppress warning that matrix.org is in above list

# Federation Configuration
allow_public_rooms_over_federation: false # Allow other servers to read your public room directory
federation: # Back off retrying dead servers as often
  destination_min_retry_interval: 1m
  destination_retry_multiplier: 5
  destination_max_retry_interval: 365d
federation_ip_range_blacklist: # IP address ranges to forbid for federation
  - '10.0.0.0/8'
  - '100.64.0.0/10'
  - '127.0.0.0/8'
  - '169.254.0.0/16'
  - '172.16.0.0/12'
  - '192.168.0.0/16'
  - '::1/128'
  - 'fc00::/7'
  - 'fe80::/64'

# Cache Configuration
event_cache_size: 30K
caches:
  global_factor: 1
  expire_caches: true
  cache_entry_ttl: 1080m
  sync_response_cache_duration: 2m
  per_cache_factors:
    get_current_hosts_in_room: 3
    get_local_users_in_room: 3
    get_partial_current_state_ids: 0.5
    _get_presence_for_user: 3
    get_rooms_for_user: 3
    _get_server_keys_json: 3
    stateGroupCache: 0.1
    stateGroupMembersCache: 0.2
  cache_autotuning:
    max_cache_memory_usage: 896M
    target_cache_memory_usage: 512M
    min_cache_ttl: 30s

# Garbage Collection (Cache Eviction)
gc_thresholds: [550, 10, 10]
gc_min_interval: [1s, 1m, 2m]

# Media Configuration
media_store_path: "/media" # Path where media files will be stored
media_retention:
  local_media_lifetime: 5y # Maximum time to retain local media files
  remote_media_lifetime: 30d # Maximum time to retain remote media files

# User and Room Management
allow_guest_access: false # Whether to allow guest access
auto_join_rooms: # Rooms to auto-join new users to
  - "#welcome-room:mydomain.com"
autocreate_auto_join_rooms: true # Auto-create auto-join rooms if they're missing
presence:
  enabled: true # Enable viewing/sharing of online status and last active time
push:
  include_content: true # Include content of events in push notifications
user_directory:
  enabled: true # Whether to maintain a user directory
  search_all_users: true # Whether to include all users in user directory search results
  prefer_local_users: true # Whether to give local users higher search result ranking

# Data Retention
retention:
  enabled: false # Whether to enable automatic data retention policies
forget_rooms_on_leave: true # Automatically forget rooms when leaving them
forgotten_room_retention_period: 1d # Purge rooms this long after all local users forgot it

# URL Preview Configuration
url_preview_enabled: true # Whether to enable URL previews in messages
url_preview_accept_language: # Language preferences for URL preview content
  - 'en-GB'
  - 'en-US;q=0.9'
  - '*;q=0.8'
url_preview_ip_range_blacklist: # Forbid previews for URLs at IP addresses in these ranges
  - '10.0.0.0/8'
  - '100.64.0.0/10'
  - '127.0.0.0/8'
  - '169.254.0.0/16'
  - '172.16.0.0/12'
  - '192.168.0.0/16'
  - '::1/128'
  - 'fc00::/7'
  - 'fe80::/64'

# SSO Configuration
oidc_providers:
  - idp_id: authentik
    idp_name: "SSO"
    idp_icon: "mxc://mydomain.com/SomeImageURL"
    discover: true
    issuer: "https://auth.fostered.uk/application/o/matrix/"
    client_id: "SuperSecretClientId"
    client_secret: "SuperSecretClientSecret"
    scopes: ["openid", "profile", "email"]
    allow_existing_users: true
    user_mapping_provider:
      config:
        localpart_template: "{{ user.preferred_username }}"
        display_name_template: "{{ user.name|capitalize }}"
        email_template: "{{ user.email }}"

# Email Configuration
email:
  enable_notifs: true # Whether to enable email notifications
  smtp_host: "smtp.mydomain.com" # Hostname of the SMTP server
  smtp_port: 587 # TCP port to connect to SMTP server
  smtp_user: "SuperSecretEmailUser" # Username to connect to SMTP server
  smtp_pass: "SuperSecretEmailPass" # Password to connect to SMTP server
  require_transport_security: True # Require transport security (TLS) for SMTP
  notif_from: "Matrix <noreply@mydomain.com>" # The From address for notification emails
  app_name: Matrix # Name of the app to use in email templates
  notif_for_new_users: True # Enable notifications for new users

# Security and Authentication
form_secret: "SuperSecretValue1" # Secret for preventing CSRF attacks
macaroon_secret_key: "SuperSecretValue2" # Secret for generating macaroons
registration_shared_secret: "SuperSecretValue3" # Shared secret for registration
recaptcha_public_key: "SuperSecretValue4" # Public key for reCAPTCHA
recaptcha_private_key: "SuperSecretValue5" # Private key for reCAPTCHA
worker_replication_secret: "SuperSecretValue6" # Secret for communication between Synapse and workers

In this case, I've included typical configuration for Authentik in case you want to use SSO instead of Synapse's built-in password database - it's perfectly safe to omit this oidc_providers: section if you're not using SSO, but the official Authentik guide is quite quick and easy if you do wish to use it after installing Authentik.

Cache Optimisation

Most of the example configuration above is fairly standard, however of particular note to performance tuning is the cache configuration.

The defaults (at time of writing) are below and in the official documentation at event_cache_size and caches:

event_cache_size: 10K
caches:
  global_factor: 0.5
  expire_caches: true
  cache_entry_ttl: 30m
  sync_response_cache_duration: 2m

In this default case:

  • All of the caches (including the event_cache_size) are halved (so each worker can only actually hold 5,000 events as a maximum)
  • Every entry in the cache expires within 30 minutes
  • cache_autotuning is disabled, so entries leave the cache after 30 minutes or when the server needs to cache something and there isn't enough space to store it.

In particular, that last option is a problem, as we have multiple containers, so we don't want every container seeking to fill its caches to the max then waiting for the expiry time to lose entries that have only been read once!

I've recommended the following config, which instead:

  • Increases the number of events we can cache to lower load on the database
  • Enable cache_autotuning to remove entries that aren't frequently accessed
  • Allow entries to stay in cache longer when they're used frequently
  • Modified the limit to expand caches that are frequently accessed by large federated rooms, and restricted ones that are less frequently reused
event_cache_size: 30K
caches:
  global_factor: 1
  expire_caches: true
  cache_entry_ttl: 1080m
  sync_response_cache_duration: 2m
  per_cache_factors:
    get_current_hosts_in_room: 3
    get_local_users_in_room: 3
    get_partial_current_state_ids: 0.5
    _get_presence_for_user: 3
    get_rooms_for_user: 3
    _get_server_keys_json: 3
    stateGroupCache: 0.1
    stateGroupMembersCache: 0.2
  cache_autotuning:
    max_cache_memory_usage: 896M
    target_cache_memory_usage: 512M
    min_cache_ttl: 30s

Furthermore, as this is designed to be a server with more limited RAM, we've updated the "garbage collection" thresholds, so Synapse can quickly clean up older cached entries to make sure we're keeping a healthy amount of cache without running out of memory:

gc_thresholds: [550, 10, 10]
gc_min_interval: [1s, 1m, 2m]