Installing the new Dendrite Matrix Homeserver on Debian Buster

This post contains my personal notes on how to set up Dendrite, Matrix.org’s second-generation homeserver written in Go. These notes are not yet complete, but do contain the basics to get things going. Please note that Dendrite is in early beta and not yet recommended for use in production environments. For more information, refer to the README.md on Github and this blogpost on Matrix.org. For more information on Dendrite, have a look at the official Dendrite documentation.

This post assumes you are using a system running Debian, either on bare-metal, virtualised or in an LXC container.

Setup apt, unattended-upgrades and dependencies

apt update
apt dist-upgrade -y

apt install sudo -y

sudo tee /etc/apt/sources.list << EOF > /dev/null
deb http://deb.debian.org/debian/ buster main non-free contrib
deb-src http://deb.debian.org/debian/ buster main non-free contrib

deb http://security.debian.org/debian-security buster/updates main contrib non-free
deb-src http://security.debian.org/debian-security buster/updates main contrib non-free

# buster-updates, previously known as 'volatile'
deb http://deb.debian.org/debian/ buster-updates main contrib non-free
deb-src http://deb.debian.org/debian/ buster-updates main contrib non-free

# buster-backports, previously on backports.debian.org
deb http://deb.debian.org/debian/ buster-backports main contrib non-free
deb-src http://deb.debian.org/debian/ buster-backports main contrib non-free
EOF

sudo apt update

sudo apt install sudo nano unattended-upgrades wget ssh postgresql git fail2ban -y

sed -i "s/\/\/      \"origin=Debian,codename=\${distro_codename}-updates\";/        \"origin=Debian,codename=\${distro_codename}-updates\";/g" /etc/apt/apt.conf.d/50unattended-upgrades
sed -i "s/\/\/Unattended-Upgrade::Automatic-Reboot \"false\";/Unattended-Upgrade::Automatic-Reboot \"true\";/g" /etc/apt/apt.conf.d/50unattended-upgrades

Add Dendrite user

sudo adduser \
    --system \
    --shell /bin/bash \
    --gecos 'Dendrite' \
    --group \
    --disabled-password \
    --home /opt/dendrite \
    dendrite

sudo -u postgres createuser -P dendrite

for i in account device mediaapi syncapi roomserver signingkeyserver federationsender appservice e2ekey naffka; do
    sudo -u postgres createdb -O dendrite dendrite_$i
done

Install Golang

wget https://golang.org/dl/go1.15.6.linux-amd64.tar.gz
sudo tar -C /usr/local -xzf go1.15.6.linux-amd64.tar.gz

sudo tee -a /etc/profile <<EOF >> /dev/null
export PATH=\$PATH:/usr/local/go/bin
EOF

Logout & login again

Clone repository

git clone https://github.com/matrix-org/dendrite
cd dendrite
git tag # Lookup most recent version
git checkout v0.3.3

go build -o bin/dendrite-monolith-server ./cmd/dendrite-monolith-server
go build -o bin/generate-keys ./cmd/generate-keys

cd ..

Move to folder

sudo mv dendrite /opt/
sudo chown -R dendrite:dendrite /opt/dendrite

Setup

sudo mkdir /var/log/dendrite
sudo chown -R dendrite:dendrite /var/log/dendrite

sudo mkdir /etc/dendrite
sudo /opt/dendrite/bin/generate-keys --private-key /etc/dendrite/matrix_key.pem
sudo nano /etc/dendrite/dendrite.yaml
# This is the Dendrite configuration file.
#
# The configuration is split up into sections - each Dendrite component has a
# configuration section, in addition to the "global" section which applies to
# all components.
#
# At a minimum, to get started, you will need to update the settings in the
# "global" section for your deployment, and you will need to check that the
# database "connection_string" line in each component section is correct.
#
# Each component with a "database" section can accept the following formats
# for "connection_string":
#   SQLite:     file:filename.db
#               file:///path/to/filename.db
#   PostgreSQL: postgresql://user:pass@hostname/database?params=...
#
# SQLite is embedded into Dendrite and therefore no further prerequisites are
# needed for the database when using SQLite mode. However, performance with
# PostgreSQL is significantly better and recommended for multi-user deployments.
# SQLite is typically around 20-30% slower than PostgreSQL when tested with a
# small number of users and likely will perform worse still with a higher volume
# of users.
#
# The "max_open_conns" and "max_idle_conns" settings configure the maximum
# number of open/idle database connections. The value 0 will use the database
# engine default, and a negative value will use unlimited connections. The
# "conn_max_lifetime" option controls the maximum length of time a database
# connection can be idle in seconds - a negative value is unlimited.

# The version of the configuration file.
version: 1

# Global Matrix configuration. This configuration applies to all components.
global:
  # The domain name of this homeserver.
  server_name: example.org

  # The path to the signing private key file, used to sign requests and events.
  private_key: /etc/dendrite/matrix_key.pem

  # The paths and expiry timestamps (as a UNIX timestamp in millisecond precision)
  # to old signing private keys that were formerly in use on this domain. These
  # keys will not be used for federation request or event signing, but will be
  # provided to any other homeserver that asks when trying to verify old events.
  # old_private_keys:
  # - private_key: old_matrix_key.pem
  #   expired_at: 1601024554498

  # How long a remote server can cache our server signing key before requesting it
  # again. Increasing this number will reduce the number of requests made by other
  # servers for our key but increases the period that a compromised key will be
  # considered valid by other homeservers.
  key_validity_period: 168h0m0s

  # Lists of domains that the server will trust as identity servers to verify third
  # party identifiers such as phone numbers and email addresses.
  trusted_third_party_id_servers:
  - matrix.org
  - vector.im

  # Configuration for Kafka/Naffka.
  kafka:
    # List of Kafka broker addresses to connect to. This is not needed if using
    # Naffka in monolith mode.
    addresses:
      - localhost:2181

    # The prefix to use for Kafka topic names for this homeserver. Change this only if
    # you are running more than one Dendrite homeserver on the same Kafka deployment.
    topic_prefix: Dendrite

    # Whether to use Naffka instead of Kafka. This is only available in monolith
    # mode, but means that you can run a single-process server without requiring
    # Kafka.
    use_naffka: true

    # Naffka database options. Not required when using Kafka.
    naffka_database:
      connection_string: postgres://dendrite:____YOURPASSWORD____@localhost/dendrite_naffka
      max_open_conns: 10
      max_idle_conns: 2
      conn_max_lifetime: -1

  # Configuration for Prometheus metric collection.
  metrics:
    # Whether or not Prometheus metrics are enabled.
    enabled: false

    # HTTP basic authentication to protect access to monitoring.
    basic_auth:
      username: metrics
      password: metrics

# Configuration for the Appservice API.
app_service_api:
  internal_api:
    listen: http://localhost:7777
    connect: http://localhost:7777
  database:
    connection_string: postgres://dendrite:____YOURPASSWORD____@localhost/dendrite_appservice
    max_open_conns: 10
    max_idle_conns: 2
    conn_max_lifetime: -1

  # Appservice configuration files to load into this homeserver.
  config_files: []

# Configuration for the Client API.
client_api:
  internal_api:
    listen: http://localhost:7771
    connect: http://localhost:7771
  external_api:
    listen: http://[::]:8071

  # Prevents new users from being able to register on this homeserver, except when
  # using the registration shared secret below.
  registration_disabled: false

  # If set, allows registration by anyone who knows the shared secret, regardless of
  # whether registration is otherwise disabled.
  registration_shared_secret: ""

  # Whether to require reCAPTCHA for registration.
  enable_registration_captcha: false

  # Settings for ReCAPTCHA.
  recaptcha_public_key: ""
  recaptcha_private_key: ""
  recaptcha_bypass_secret: ""
  recaptcha_siteverify_api: ""

  # TURN server information that this homeserver should send to clients.
  turn:
    turn_user_lifetime: ""
    turn_uris: []
    turn_shared_secret: ""
    turn_username: ""
    turn_password: ""

  # Settings for rate-limited endpoints. Rate limiting will kick in after the
  # threshold number of "slots" have been taken by requests from a specific
  # host. Each "slot" will be released after the cooloff time in milliseconds.
  rate_limiting:
    enabled: true
    threshold: 5
    cooloff_ms: 500

# Configuration for the EDU server.
edu_server:
  internal_api:
    listen: http://localhost:7778
    connect: http://localhost:7778

# Configuration for the Federation API.
federation_api:
  internal_api:
    listen: http://localhost:7772
    connect: http://localhost:7772
  external_api:
    listen: http://[::]:8072

  # List of paths to X.509 certificates to be used by the external federation listeners.
  # These certificates will be used to calculate the TLS fingerprints and other servers
  # will expect the certificate to match these fingerprints. Certificates must be in PEM
  # format.
  federation_certificates: []

# Configuration for the Federation Sender.
federation_sender:
  internal_api:
    listen: http://localhost:7775
    connect: http://localhost:7775
  database:
    connection_string: postgres://dendrite:____YOURPASSWORD____@localhost/dendrite_federationsender
    max_open_conns: 10
    max_idle_conns: 2
    conn_max_lifetime: -1

  # How many times we will try to resend a failed transaction to a specific server. The
  # backoff is 2**x seconds, so 1 = 2 seconds, 2 = 4 seconds, 3 = 8 seconds etc.
  send_max_retries: 16

  # Disable the validation of TLS certificates of remote federated homeservers. Do not
  # enable this option in production as it presents a security risk!
  disable_tls_validation: false

  # Use the following proxy server for outbound federation traffic.
  proxy_outbound:
    enabled: false
    protocol: http
    host: localhost
    port: 8080

# Configuration for the Key Server (for end-to-end encryption).
key_server:
  internal_api:
    listen: http://localhost:7779
    connect: http://localhost:7779
  database:
    connection_string: postgres://dendrite:____YOURPASSWORD____@localhost/dendrite_e2ekey
    max_open_conns: 10
    max_idle_conns: 2
    conn_max_lifetime: -1

# Configuration for the Media API.
media_api:
  internal_api:
    listen: http://localhost:7774
    connect: http://localhost:7774
  external_api:
    listen: http://[::]:8074
  database:
    connection_string: postgres://dendrite:____YOURPASSWORD____@localhost/dendrite_mediaapi
    max_open_conns: 10
    max_idle_conns: 2
    conn_max_lifetime: -1

  # Storage path for uploaded media. May be relative or absolute.
  base_path: ./media_store

  # The maximum allowed file size (in bytes) for media uploads to this homeserver
  # (0 = unlimited).
  max_file_size_bytes: 10485760

  # Whether to dynamically generate thumbnails if needed.
  dynamic_thumbnails: false

  # The maximum number of simultaneous thumbnail generators to run.
  max_thumbnail_generators: 10

  # A list of thumbnail sizes to be generated for media content.
  thumbnail_sizes:
  - width: 32
    height: 32
    method: crop
  - width: 96
    height: 96
    method: crop
  - width: 640
    height: 480
    method: scale

# Configuration for the Room Server.
room_server:
  internal_api:
    listen: http://localhost:7770
    connect: http://localhost:7770
  database:
    connection_string: postgres://dendrite:____YOURPASSWORD____@localhost/dendrite_roomserver
    max_open_conns: 10
    max_idle_conns: 2
    conn_max_lifetime: -1

# Configuration for the Signing Key Server (for server signing keys).
signing_key_server:
  internal_api:
    listen: http://localhost:7780
    connect: http://localhost:7780
  database:
    connection_string: postgres://dendrite:____YOURPASSWORD____@localhost/dendrite_signingkeyserver
    max_open_conns: 10
    max_idle_conns: 2
    conn_max_lifetime: -1

  # Perspective keyservers to use as a backup when direct key fetches fail. This may
  # be required to satisfy key requests for servers that are no longer online when
  # joining some rooms.
  key_perspectives:
  - server_name: matrix.org
    keys:
    - key_id: ed25519:auto
      public_key: Noi6WqcDj0QmPxCNQqgezwTlBKrfqehY1u2FyWP9uYw
    - key_id: ed25519:a_RXGa
      public_key: l8Hft5qXKn1vfHrg3p4+W8gELQVo8N13JkluMfmn2sQ

  # This option will control whether Dendrite will prefer to look up keys directly
  # or whether it should try perspective servers first, using direct fetches as a
  # last resort.
  prefer_direct_fetch: false

# Configuration for the Sync API.
sync_api:
  internal_api:
    listen: http://localhost:7773
    connect: http://localhost:7773
  external_api:
    listen: http://[::]:8073
  database:
    connection_string: postgres://dendrite:____YOURPASSWORD____@localhost/dendrite_syncapi
    max_open_conns: 10
    max_idle_conns: 2
    conn_max_lifetime: -1

# Configuration for the User API.
user_api:
  internal_api:
    listen: http://localhost:7781
    connect: http://localhost:7781
  account_database:
    connection_string: postgres://dendrite:____YOURPASSWORD____@localhost/dendrite_account
    max_open_conns: 10
    max_idle_conns: 2
    conn_max_lifetime: -1
  device_database:
    connection_string: postgres://dendrite:____YOURPASSWORD____@localhost/dendrite_device
    max_open_conns: 10
    max_idle_conns: 2
    conn_max_lifetime: -1

# Configuration for Opentracing.
# See https://github.com/matrix-org/dendrite/tree/master/docs/tracing for information on
# how this works and how to set it up.
tracing:
  enabled: false
  jaeger:
    serviceName: ""
    disabled: false
    rpc_metrics: false
    tags: []
    sampler: null
    reporter: null
    headers: null
    baggage_restrictions: null
    throttler: null

# Logging configuration, in addition to the standard logging that is sent to
# stdout by Dendrite.
logging:
- type: file
  level: info
  params:
    path: /var/log/dendrite
sudo ln -s /etc/dendrite/dendrite.yaml /opt/dendrite/dendrite.yaml
sudo chmod -R 700 /etc/dendrite
sudo chown -R dendrite:dendrite /etc/dendrite

Systemd service

sudo nano /etc/systemd/system/dendrite.service
[Unit]
Description=Dendrite (Matrix Homeserver)
After=syslog.target
After=network.target
After=postgresql.service

[Service]
Environment="GODEBUG=madvdontneed=1"
RestartSec=2s
Type=simple
User=dendrite
Group=dendrite
WorkingDirectory=/opt/dendrite/
ExecStart=/opt/dendrite/bin/dendrite-monolith-server
Restart=always

[Install]
WantedBy=multi-user.target
sudo systemctl enable dendrite --now

Monitor Dendrite

sudo journalctl -u dendrite -f

Exit journalctl with Ctrl + C

Nginx reverse proxy settings

This section assumes nginx is already running and valid certificates have been generated through Letsencrypt.

sudo nano /etc/nginx/conf.d/dendrite.conf
server {
    listen 80;
    server_name matrix.example.org;

    location /.well-known/acme-challenge/ {
        root /var/www/acme;
    }

    location / {
        return 301 https://$host$request_uri;
    }
}

server {
    listen 443 ssl;
    server_name matrix.example.org;

    ssl_certificate /etc/letsencrypt/live/matrix.example.org/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/matrix.example.org/privkey.pem;
    ssl_dhparam /etc/ssl/certs/dhparam_4096.pem;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_prefer_server_ciphers on;
    ssl_ciphers "ECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH";
    ssl_ecdh_curve secp384r1;
    ssl_session_cache shared:SSL:10m;
    ssl_session_tickets off;
    ssl_stapling on;
    ssl_stapling_verify on;
    resolver 127.0.0.1 valid=300s;
    resolver_timeout 5s;
    add_header Strict-Transport-Security "max-age=63072000; preload";
    keepalive_timeout 300s;

    proxy_set_header Host      $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_read_timeout         600;

    location /.well-known/matrix/server {
        default_type application/json;
        return 200 '{ "m.server": "matrix.example.org:443" }';
    }

    location /.well-known/matrix/client {
        default_type application/json;
        return 200 '{ "m.homeserver": { "base_url": "https://matrix.example.org" } }';
        add_header "Access-Control-Allow-Origin" *;
    }

    location /_matrix {
        proxy_pass http://your-dendrite-ip-or-hostname:8008;
    }
}
sudo nginx -t
sudo systemctl restart nginx

Check

Browse to https://matrix.example.org/_matrix/federation/v1/version. The URL should return

{
  "server": {
    "version": "0.1.0",
    "name": "Dendrite"
  }
}

Upgrading Dendrite

Stop Dendrite

sudo systemctl stop dendrite

Become dendrite user

sudo su - dendrite

Upgrade

git pull

git tag

git checkout v0.3.3

/usr/local/go/bin/go build -o bin/dendrite-monolith-server ./cmd/dendrite-monolith-server
/usr/local/go/bin/go build -o bin/generate-keys ./cmd/generate-keys

exit

Restart Dendrite

sudo systemctl restart dendrite

TODO

  • Fail2ban support
  • TURN
  • Recaptcha
  • Metrics
  • Backups
  • Deploy Element
  • Lower log level, find out about possible log levels.

Sources

Updates

  • 2020-12: Update to latest versions