hello
recently i've set up a matrix server and i went through a lot of pain, so i'm here to document issues i've faced and hopefully i can help more people set up their homeservers quicker and with less issues
also, before we start, i want to clarify that all commands that start with #
must be ran as the root user ( for example through sudo
or su
), and $
should be ran as normal user ( for example matrix
or user
or something ), unless stated otherwise
# setup
i wouldn't suggest going below the contabo VPS S SSD hardware-level because it may get slow and painful, especially when joining bigger rooms, i'd even suggest going with contabo VPS M SSD, which is why i'll upgrade soon
# delegation of the main domain
i assume you won't be running your website ( say like https://ari.lt/ ) on the same server you run your matrix server, in my case, i actually even couldn't because of how my website is hosted on netlify and ye, but regardless, i'd very much suggest running matrix ( dendrite )
i personally went for the .well-known delegation method, but you can go for anything you like as there's multiple methods
here's how my .well-known stuff looks :
.well-known/matrix/client
:
{
"m.homeserver": {
"base_url": "https://matrix.ari.lt"
}
}
.well-known/matrix/server
:
{
"m.server": "matrix.ari.lt:443"
}
as seen at https://ari.lt/git, they also don't have to be pretty-printed, i don't know why i made them pretty, but it's fine
few key notes :
- do not forget the port in
.well-known/matrix/server
, it is not implicit, i don't remember the default port, but prefer to be explicit - the files must point to your matrix server ( where dendrite will be hosted )
- make sure that the files return
Content-Type
header as JSON, aka application/json
- make sure CORS is set up correctly
Access-Control-Allow-Origin
= *
Access-Control-Allow-Methods
= GET
this is the easy part
# golang
before anything, we will need to install golang, on debian you can do apt install go-golang
, but that may install an old version of go, which isn't desirable, here's how i did it :
this gave me the latest go language compiler, which we will use to compile dendrite as it's written in go
# installing other dependencies
other dependencies are defined in https://matrix-org.github.io/dendrite/installation/planning#dependencies, but at the moment they're :
- go ( already covered in # golang )
- postgresql database
- built-in NATS server ( we don't need to do anything here, dendrite comes with one )
- reverse proxy, such as nginx, which we will use in this case
and for SSL stuff we will also add certbot
to our dependencies so we could have a secure SSL connection
to install them, you can run the following :
# apt install postgresql postgresql-client nginx certbot python3-certbot-nginx
postgresql
and postgresql-client
for postgresql dependency and interface nginx
as our reverse proxy certbot
and python3-certbot-nginx
for SSL things
# preparing database
preparing the database is fairly easy as per the matrix dendrite database setup
firstly, you need to start and enable the postgresql service :
# systemctl enable --now postgresql
next, run the following to switch to postgresql control user :
# su postgres
$ cd
now you should be in the postgres user's home dir, now you will have to create the role for dendrite, set its password and create its database, but before, i have to warn you to create a password such as it shouldn't include non-url-safe characters, else it may be a pain to configure dendrite in the future, this is why i'd recommend you just generate the password using the following command :
$ head -n 16 /dev/urandom | base64 -w 0 | shuf | sed 's/[^A-Za-z0-9]//g' | head -c 123
and then using these commands to create the role, set its password and create the database :
$ createuser -P dendrite
$ createdb -O dendrite -E UTF-8 dendrite
now you're all set with the database
# compiling dendrite
firstly, let's set up the user we'll run dendrite on, it is a good practice to run applications such as this under lowest possible privileges so we don't run into nasty attacks in the future, here's how you do it :
run the following command
# useradd -m matrix
this will create a new user called matrix
with its own /home/matrix/
directory, we will run dendrite under this user,, next -- set the password for the user :
# passwd matrix
make sure to use a secure password, may i recommend pwdtools ? though you can use anything
you may also want to run this to make the home directory of this user only readable by that user :
$ chmod 700 -R /home/matrix/
but it's optional
then, switch to the matrix user, you can use any user to do this :
$ su matrix
now as you're the matrix
user, you should go into your ~
( home ) directory :
$ cd
now as you're the matrix user in its home direcotory, download the latest release tarball ( .tar.gz
) off https://github.com/matrix-org/dendrite/releases/latest and extract it
at the moment for me it's https://github.com/matrix-org/dendrite/archive/refs/tags/v0.13.5.tar.gz so i'll assume the same, although for future readers -- please grab the latest version, here's an example of how to download it and extract it :
$ curl -fLO https://github.com/matrix-org/dendrite/archive/refs/tags/v0.13.5.tar.gz
$ tar xvf v0.13.5.tar.gz
$ cd dendrite-0.13.5/
now you should end up in the latest release of dendrite
according to https://matrix-org.github.io/dendrite/installation/manual/build you should now run the following :
$ go build -o bin/ ./cmd/...
keep in mind it's LITERALLY go build -o bin/ ./cmd/...
and not for example go build -o bin/ ./cmd/*
, it's a literal elipsis, run the command as-is with the 3 dots as go is weird and quirky like that i guess
this should build dendrite, keep in mind this will be network and resource heavy
now you can install it :
$ go install ./cmd/dendrite
and your dendrite installation should end up in ~/go/bin/dendrite
:)
# signing keys
now, you will generate signing keys for your matrix encryption, which will be used in authentication of federation requests as explained here, the tutorial for setting up keys in dendrite
run the following command in the dendrite directory ( the one you ran commands in # compiling dendrite ) :
$ ./bin/generate-keys --private-key matrix_key.pem
never share this key with anyone
# configuring dendrite
this part is based off my own config of dendrite, which is set up by help of people, my own research and the setup docs, keep up-to-date with my configuration and the docs as this section may get outdated, although i'll do my best to keep this up to date as long as i run the ari-web matrix server
firstly copy the example config :
$ cp dendrite-sample.yaml dendrite.yaml
and now, open it in your favourite text editor, such as vim
for example, maybe nano
even :
$ vim dendrite.yaml
now, i will cover only some parts of the config which you may want to change, but also the default config includes a lot of comments, so you may want to look through all of it and see what you want or don't
global
:
server_name
-- this should be the domain you are delegating from, for example ari.lt
has the .well-known
delegation to matrix.ari.lt
so the value of server_name
will be ari.lt
database.connection_string
-- this is the connection string of your postgresql database, this should be a url as follows : postgresql://dendrite:<password>@127.0.0.1/dendrite
, replace <password>
with your password, for example postgresql://dendrite:password123@127.0.0.1/dendrite
well_known_server_name
and well_known_client_name
-- these two keys should have the same value of https://<your matrix domain>:443
, for example https://matrix.ari.lt:443
, the domain must be the same as your matrix server, not delegated domain ( so matrix.ari.lt
and not ari.lt
) - make sure
disable_federation
is set to false
presence.*
-- set all keys that are false
to true
, if you want presence to be a thing ( such as typing, online status, etc ), this is optional
client_api
:
- i'd suggest setting
registration_disabled
and guests_disabled
to true
so you'd have full control over what people have accounts, if you want open registrations you may want to set both of them to false
or just registration_disabled
to false
, depending on your wants, you will be able to create new accounts using ./bin/create-account
later on regardless - if you disabled registrations, you'll need to set
registration_shared_secret
to some value, you can use the password generator command from before -- head -n 16 /dev/urandom | base64 -w 0 | shuf | sed 's/[^A-Za-z0-9]//g' | head -c 123
to generate something good enough - you may also want to set
rate_limiting.exempt_user_ids
to yourself ( like @ari:ari.lt
as an example ), maybe even change the rate limiting in general
sync_api
:
- set the
real_ip_header
to X-Real-IP
, we will use this in the future - if you want search functionality, you may want to set
search.enable
to true
user_api
:
- if you'll have / already have a main / lounge room, you may want to add it to
auto_join_rooms
, like #root:ari.lt
for example
that's pretty much it with the configuration of dendrite, although i'd still suggest going through all config options at least once and thinking if you want them or not :)
# running dendrite
to run dendrite you can just run
$ ~/go/bin/dendrite -config ./dendrite.yaml
and if you want to run it in the background you just run this :
$ ~/go/bin/dendrite -config ./dendrite.yaml & disown
simple as that, now dendrite will be listening on port 8008
# dns records
the A
record is required
matrix.yourdomain.tld 3600 IN A <server ip>
matrix.yourdomain.tld 3600 IN CAA 0 issue <issuer>
you may remove CAA
if you disable all the fancy SSL stuff in nginx ( which we're about to configure )
you can also optionally add AAAA
for ipv6 support
for ari-web i've set it up like this :
matrix.ari.lt 3600 IN A 62.171.174.136
matrix.ari.lt 3600 IN CAA 0 issue letsencrypt.org
# configuring nginx
tldr # final nginx config
in our case we will use nginx as our reverse proxy, this i will base off my own nginx config for my vps
firstly, you need to make sure if either user www-data
or http
exists, you can do that by running
$ cat /etc/passwd
which will show you all users
if none do, run
# useradd www-data # or http
after that, make sure that /etc/nginx/mime.types
exists :
$ ls /etc/nginx/mime.types
if not, run the following command :
# curl https://raw.githubusercontent.com/nginx/nginx/master/conf/mime.types -fLo /etc/nginx/mime.types
now, open up /etc/nginx/nginx.conf
in your favourite text editor and configure it :
# vim /etc/nginx/nginx.conf
we will start by setting up some base rules :
user www-data;
worker_processes auto;
pid /run/nginx.pid;
worker_rlimit_nofile 8192;
events {
use epoll;
multi_accept on;
worker_connections 4096;
}
make sure to replace www-data
with http
if you're using the http
user instead, here's what this piece of config means :
user www-data;
will make sure that the worker processes run under a low privilege user such as www-data
worker_processes auto;
makes the worker process count optimal for your cpu core count, each worker than handle thousands of connections pid /run/nginx.pid;
specifies the file where the server will write its master process id worker_rlimit_nofile 8192;
sets the limit of maximum number of file descriptors opened by this process ( every connection is a unix socket, so also a file descriptor ) use epoll;
sets the method to use to get notifications for network events, epoll
is particularly good for linux multi_accept on;
allows to handle multiple simultaneous connections worker_connections 4096;
sets the limit of maximum number of connections per worker
next, we'll set up some basic config for our server :
http {
include mime.types;
default_type application/octet-stream;
access_log off;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 120;
types_hash_max_size 2048;
server_names_hash_bucket_size 256;
sendfile on;
}
you can dig into this config deeper, but abstractly :
default_type application/octet-stream;
sets the default mime type to application/octet-stream
( binary data ) access_log off;
turns off the access log so we don't log requests and their IPs - sets up some TCP options
- sets
types_hash_max_size
, it's the mime type hash table size, this is for different types of mime types for different files as matrix also works on file uploading server_names_hash_bucket_size 256;
is for server directives, how much memory it's allowed to use sendfile on;
is for file uploads
now, we can set up a basic server
:
http {
...
server {
listen 80;
listen [::]:80;
server_name matrix.yourdomain.tld;
access_log off;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl;
listen [::]:443 ssl;
listen 8448 ssl http2 default_server;
listen [::]:8448 ssl http2 default_server;
server_name matrix.yourdomain.tld;
access_log off;
}
}
here you can also now start nginx :
# systemctl enable --now nginx
this sets up the basic requirements for a server, make sure to replace yourdomain.tld / matrix.yourdomain.tld with whatever your preferred domain is, 443 is the https ( tls ) port and 8448 is the secure federation port,, this is where we have to take a step back, save our nginx config and move on for a little bit
# tls ( https )
to set up tls ( https ) on our server now, we will have to run this command :
certbot certonly --nginx
and follow the directions on the screen
now, as you have that set up, you can continue setting up your proxy, add SSL stuff :
http {
...
server {
listen 443 ssl;
listen [::]:443 ssl;
listen 8448 ssl http2 default_server;
listen [::]:8448 ssl http2 default_server;
server_name matrix.yourdomain.tld;
access_log off;
ssl_certificate /etc/letsencrypt/live/matrix.yourdomain.tld/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/matrix.yourdomain.tld/privkey.pem;
ssl_trusted_certificate /etc/letsencrypt/live/matrix.yourdomain.tld/fullchain.pem;
ssl_stapling on;
ssl_stapling_verify on;
ssl_session_timeout 1d;
ssl_session_cache shared:MozSSL:10m;
ssl_session_tickets off;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256';
ssl_prefer_server_ciphers on;
}
}
no, we dont need to touch the :80
one, that will always stay the same as it'll just redirect to https
i won't explain these options, but basically only two lines of SSL things are required :
ssl_certificate /etc/letsencrypt/live/matrix.yourdomain.tld/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/matrix.yourdomain.tld/privkey.pem;
other stuff is sanity and security, i won't dig deep into it but basically this enables ssl stapling and also sets up some secure preferred ciphers so we know that secure encryption is happening at all times
if you didn't choose to use the CAA
record you may use only those two lines instead of all those ssl_*
configurations
and now, you are done setting ssl up on nginx
# after ssl ( https )
now, we add our locations :
http {
...
server {
listen 443 ssl;
listen [::]:443 ssl;
listen 8448 ssl http2 default_server;
listen [::]:8448 ssl http2 default_server;
server_name matrix.yourdomain.tld;
...
location = / {
access_log off;
return 301 https://$server_name/_matrix/static/;
}
location ~ ^(/_matrix|/_synapse/client) {
access_log off;
proxy_pass http://127.0.0.1:8008;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
client_max_body_size 512M;
proxy_max_temp_file_size 0;
proxy_buffering off;
}
}
}
location = /
sets up the /
location of your server, it'll always redirect to the dendrite static page so once you visit matrix.yourdomain.tld
, it'll always redirect to the welcome page, so it doesn't look as boring, although you can just remove that, it's optional
now location ~ ^(/_matrix|/_synapse/client)
is where the actual federation stuff happens, as always we turn off the access log and we pass our requests to our local dendrite instance running on port 8008
and then set up the following things :
- always set the http version to 1.1
- set a couple of headers ( such as
X-Real-IP
from before, this is used for multiple things ) client_max_body_size 512M;
states that the accepted body of a client cannot exceed 512 megabytes, you can increase or decrease the size, this is only ever an issue in file uploads proxy_max_temp_file_size 0;
sets the maximum size of a temp file, keeping it 0
disables the buffering, which means the content coming FROM the proxy directly to the user, if not this breaks downloads in matrix and makes it so they break and only partially deliver something proxy_buffering off;
this is like proxy_max_temp_file_size 0
but instead its TO user not FROM user
# final nginx config
after all this work we end up with a config something like :
user www-data;
worker_processes auto;
pid /run/nginx.pid;
worker_rlimit_nofile 8192;
events {
use epoll;
multi_accept on;
worker_connections 4096;
}
http {
include mime.types;
default_type application/octet-stream;
access_log off;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 120;
types_hash_max_size 2048;
server_names_hash_bucket_size 256;
sendfile on;
server {
listen 80;
listen [::]:80;
server_name matrix.yourdomain.tld;
access_log off;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl;
listen [::]:443 ssl;
listen 8448 ssl http2 default_server;
listen [::]:8448 ssl http2 default_server;
server_name matrix.yourdomain.tld;
access_log off;
ssl_certificate /etc/letsencrypt/live/matrix.yourdomain.tld/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/matrix.yourdomain.tld/privkey.pem;
ssl_trusted_certificate /etc/letsencrypt/live/matrix.yourdomain.tld/fullchain.pem;
ssl_stapling on;
ssl_stapling_verify on;
ssl_session_timeout 1d;
ssl_session_cache shared:MozSSL:10m;
ssl_session_tickets off;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256';
ssl_prefer_server_ciphers on;
location = / {
access_log off;
return 301 https://$server_name/_matrix/static/;
}
location ~ ^(/_matrix|/_synapse/client) {
access_log off;
proxy_pass http://127.0.0.1:8008;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
client_max_body_size 512M;
proxy_max_temp_file_size 0;
proxy_buffering off;
}
}
}
you can now save the file and quit the editor, also, a tip : avoid gzip compression, it kills performance and cpu on your vps, it's painful, stick to vanilla
you can also add this to http
block to enable HSTS preload with which you can apply to hstspreload.org :
http {
...
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload";
...
}
# finalizing
now, after all this your matrix server is almost done, all you have to do is :
log into the matrix user :
$ su matrix
change to its home :
$ cd
kill dendrite :
$ pkill -f dendrite
restart nginx
# systemctl restart nginx
restart dendrite :
$ ~/go/bin/dendrite -config ./dendrite.yaml & disown
# sanity check
now, as everything is up and running, make sure everything's okay by going to matrix.yourdomain.tld
, it should show you the dendrite index page, if it doesn't, please verify everything and make sure everything's okay
if you cannot figure it out, you can come and ask in #root:ari.lt or #dendrite:matrix.org
# new user
now, as everything works, you can log in as the matrix user and create a new account for yourself by going into the directory which you built dendrite in ( the one with bin/
directory in it ) and run this :
$ ./bin/create-account --config dendrite.yaml -username some_username -admin
this will prompt you for a password, dendrite doesn't seem to like passwords over 72 characters, so make sure it fits
you can also create normal user accounts by doing :
$ ./bin/create-account --config dendrite.yaml -username some_username
aka removing the -admin
argument
now, you can log in with your favourite matrix client such as for example schildi or element, have fun
# concluding
i hope i could help at least a little, it took me a while to figure out issues, solve problems, find answers and come up with my own solutions, ask people, debug, etc etc etc
a lot of trouble went into this and i hope this popped up in your search engine whenever you're looking to solve such issues as :
- how to set up dendrite on nginx properly and securely
- dendrite matrix implementation on nginx not sending the full file / image ( partial send and then stream close )
- main config options in # final nginx config to look out for would be
types_hash_max_size 2048;
server_names_hash_bucket_size 256;
sendfile on;
client_max_body_size 512M;
proxy_max_temp_file_size 0;
proxy_buffering off;
- failing to sync data in ( android ) clients on dendrite matrix instance
- still unsure why web works, but i think it's related to it not sending full files
- how to properly delegate a matrix domain
- why am i getting
M_UNRECOGNIZED
in dendrite matrix server - probably because you have forwarding set up badly, make sure to not have just
location /
for example
- why am i getting request signature errors in dendrite matrix server in nginx
- also related to the
M_UNRECOGNIZED
problem, make sure to not overgeneralize the location, use location ~ ^(/_matrix|/_synapse/client)
- generally setup errors and issues with setting up dendrite with nginx and ssl
( just in case someone decides to look them up and can't find an answer )
- dendrite matrix implementation on nginx not sending the full file / image ( partial send and then stream close )
curl: (92) HTTP/2 stream 1 was not closed cleanly: INTERNAL_ERROR (err 2)
( from curl )
- failing to sync data in ( android ) clients on dendrite matrix instance
# [WARNING] Syncloop failed: Client has not connection to the server
# [WARNING] Something went wrong: - Instance of 'SyncConnectionException'
( from fluffychat android )
Initial sync:
Downloading data...
( from element android and schildichat android )
Loading... Please wait.
Oops something went wrong...
( from fluffychat android )
INFO[2023-12-26T19:32:19.277303943Z] Starting queue due to pending events or forceWakeup
( from dendrite logs )
time="2023-12-26T19:16:03.938083486Z" level=info msg="Starting queue due to pending events or forceWakeup" func="github.com/matrix-org/dendrite/federationapi/queue.(*destinationQueue).wakeQueueIfEventsPending" file="/home/matrix/dendrite/federationapi/queue/destinationqueue.go:158"
( from dendrite logs )
2023-12-26T21:42:30*130GMT+00:00Z 171 E/ /Tag: ## Sync: sync service did fail true
java.net.ProtocolException: unexpected end of stream
...
( from fluffychat android data logs )
- how to properly delegate a matrix domain
Testing matrix.ari.lt failed: mismatching server name, tested: matrix.ari.lt, got: ari.lt // Dendrite 0.13.5+9a5a567
( from @version:envs.net )
Homeserver URL does not appear to be a valid Matrix homeserver
( from element web )
- why am i getting
M_UNRECOGNIZED
in dendrite matrix server
{"errcode":"M_UNRECOGNIZED","error":"Unrecognized request"}
( from dendrite response )
- why am i getting request signature errors in dendrite matrix server in nginx
INFO[2023-12-26T00:00:08.867552600Z] Invalid request signature error="Bad signature from \"4d2.org\" with ID \"ed25519:a_MgDi\"" req.id=... req.method=PUT req.path="/\_matrix/federation/v2/invite/!...:4d2.org/$..."
( from dendrite logs )
good luck !