The far-right just had a huge success in the German elections. I'm a 51 year old white hetero cis* male in a quite solid financial situation. And I'm very concerned. From a statistically point of view, you probably have much more reason to be scared.
I'm an IT person, so my best options to help resisting the right shift is via technology. Free and secure communication is a required building block for a working democracy. Let us all move to Matrix:
Matrix is an open protocol for decentralised, secure communications.
We believe:
- People should have full control over their own communication.
- People should not be locked into centralised communication silos, but instead be free to pick who they choose to host their communication without limiting who they can reach.
- The ability to converse securely and privately is a basic human right.
- Communication should be available to everyone as a free and open, unencumbered, standard and global network.
I'm experimenting with my own Matrix instance already for a while. My goal was to come up with the perfect guide for setting up your own small personal instance. Due to time constraints this will probably never happen. But we need to get started. So lets do it like in the good old days of the internet: I'll share my current not-yet-perfect experiences. You can either learn from them or correct me. Either way we will learn and improve together in public. Lets got!
I choose Hetzner as my provider for the server. They are a German company, so they fall under EU regulations. I also appreciate their very cheap storage boxes for backup. But every Linux server will do the job.
I use Ubuntu because that's what I'm used to. The software will anyhow be run via Docker. Just make sure that your system is up-to-date and install Docker following the docs.
You need a dedicated second level domain for the following setup. Mine is
plaudern.social
("plaudern" is "to chat" in German). You'll need to subdomains.
You can choose the name as you like. I use the following ones:
I want to have the whole setup in one folder, but with clear separation between:
My resulting structure looks like this:
├── .env
├── compose.yaml
├── configs
│ ├── caddy
│ └── synapse
├── data
│ ├── caddy
│ ├── postgres
│ ├── synapse
├── logs
└── static
├── element
└── synapse-admin
The static folder contains the static content for the Element web UI and the admin interface. They are mostly just downloaded and extracted into that place. Lets ignore some minor details until later.
The compose.yaml defines which containers need to run. Before we go into details a few important remarks:
.env
file if present. The .env
contains all the secrets that must not go into the Git repository. If the
following snippets refer to an environment variable and you wonder where it
is set: It comes from the .env
.compose.yaml
are just about mapping my
folder structure in the appropriate places in the container and setting
environment variables as needed.dev
with uid=1234
and gid=1234
. Its
access rights are restricted to the project folder. I'm no way a security
expert, but that should be reasonably safe. In the future I might consider
more fine grained access control.Now we are ready to have a look at the individual containers.
The first thing we need is a Postgres database. Here is the snippet from the
compose.yaml
:
db:
user: 1234:1234
image: docker.io/postgres:17-alpine
restart: 'unless-stopped'
environment:
- POSTGRES_USER=postgres_admin
- POSTGRES_PASSWORD=${POSTGRES_ADMIN_PASSWORD}
- POSTGRES_INITDB_ARGS=--encoding=UTF-8 --lc-collate=C --lc-ctype=C
volumes:
- ./data/postgres:/var/lib/postgresql/data
- /etc/passwd:/etc/passwd:ro
All of this should be pretty straight forward. The POSTGRES_INITDB_ARGS
is
from rather old Synapse docs, so I'm not sure whether those settings are still
needed, but the work fine so far.
The last line is need due to the way Postgres starts its process. Without mapping this file into the container the user could be resolved and I got strange access problems.
Start your Postgres container:
docker compose up db
From another terminal run:
docker compose exec db psql -U postgres_admin
Create a database called synapse and a user synapse with a secure password.
Synapse is the default server
implementing the Matrix protocol. The snippet for the compose.yaml
is also
pretty straight forward.
synapse:
user: 1234:1234
image: docker.io/matrixdotorg/synapse:latest
restart: unless-stopped
environment
- SYNAPSE_CONFIG_DIR=/config
volumes:
- ./configs/synapse:/config
- ./data/synapse:/data
- ./logs/synapse:/logs
depends_on:
- db
ports:
- 8448:8448/tcp
The actuall configuration happens in the homeserver.yml
that lives in the
/config
folder. We will come back to it in a moment.
Now for the probably most important advice in this whole document: Don't use Nginx! It is a great tool, but not for this job. I spend hours trying to get the certificate stuff setup and failed miserably. I had to dig into Github issues to learn that a script I considered "official" is actually unmaintained since ages. Caddy just works and takes care of the certificates out of the box. Here is the snippet:
caddy:
user: 1234:1234
image: caddy:2
restart: unless-stopped
ports:
- "80:80"
- "443:443"
- "443:443/udp"
volumes:
- ./configs/caddy:/etc/caddy
- ./data/caddy/data:/data
- ./data/caddy/config:/config
- ./static/element:/static/element
- ./static/synapse-admin:/static/synapse-admin
That's it. These three containers are all it needs to run your own Matrix instance. A few config files are still missing. Here they are ...
Most of the config is copy & paste straight from the docs. Most notable changes:
bind_addresses
need to be generic in a Docker container to not have to
care about the acutal IP.serve_server_wellknown=true
I was able use the web interface, but
mobile clients failed to talk to the server.report_stats: true
suppress_key_server_warning: true
macaroon_secret_key: "..."
registration_shared_secret: "..."
public_baseurl: https://plaudern.social/
serve_server_wellknown: true
server_name: "plaudern.social"
pid_file: /data/homeserver.pid
listeners:
- port: 8008
tls: false
type: http
x_forwarded: true
bind_addresses: ['::1', '0.0.0.0']
resources:
- names: [client, federation]
compress: false
database:
name: psycopg2
args:
user: synapse
password: "..."
dbname: synapse
host: db
cp_min: 5
cp_max: 10
log_config: "/config/plaudern.social.log.config"
media_store_path: /data/media_store
signing_key_path: "/data/plaudern.social.signing.key"
trusted_key_servers:
- server_name: "matrix.org"
The file contains some secrets. I struggled to get the out of this file. Only a day ago somebody told me that they could be set via environment variables. I'll update the article as soon as I tested it.
The config for Caddy is even simpler:
plaudern.social {
reverse_proxy /_matrix/* synapse:8008
reverse_proxy /_synapse/client/* synapse:8008
reverse_proxy /_synapse/admin/* synapse:8008
reverse_proxy synapse:8008
}
chat.plaudern.social {
root * /static/element
file_server
}
admin.plaudern.social {
root * /static/synapse-admin
file_server
}
Yes, that's all. The two subdomains just serve static content. Download it from
the projects mentioned above, extract them. Element contains a template for config.json
.
Rename it and adapt the following snippet according to your domain names:
"default_server_config": {
"m.homeserver": {
"base_url": "https://plaudern.social",
"server_name": "plaudern.social"
},
// ...
For a personal instance is don't care too much about availabilty and fail over. Life will go on if my instance goes away for "a moment". But the data should be safe. So the missing step is an automated backup procedures. The relevant parts are:
The Element backups boils down to a database dump and backups of certain folders in the file system. I plan to setup Borg to ship backups to my Hetzner Storage Box. But that will be a follow up article.
As mentioned above, this article is written in a hurry and should be considered work in progress. It would never got published, if I would aim for perfection. The goal is to get started, share knowledge and learn together in public. If you have questions, improvements, ... don't hesitate to contact me.