What this builds
A homeserver at example.com, running on matrix.example.com, federating properly with everyone else. Postgres underneath, nginx in front, delegation handled by two small JSON files, and registration closed so that your server does not become somebody’s free relay within a week.
Federation is where these installations usually fail, and almost never because of Synapse. The server runs, local messages work, and then nothing from outside arrives. In practice the cause is nearly always delegation: the identity of the server and the address it lives at are two different things, and the mechanism connecting them is easy to get subtly wrong.
Before you start
- An R-4 is enough for a few dozen users. Synapse is memory-hungry rather than CPU-hungry, and joining large federated rooms is the one thing that will make it sweat.
- Two names in DNS:
example.com, which is your server’s identity and appears in every user id, andmatrix.example.com, which is where the software actually listens. - Certificates for both. The first only needs to serve two static files.
1. Database, with the right collation
apt update && apt install -y postgresql matrix-synapse nginx certbot
sudo -u postgres psql -c "CREATE USER synapse_user WITH PASSWORD '<a long password>';"
sudo -u postgres psql -c "CREATE DATABASE synapse ENCODING 'UTF8' LC_COLLATE='C' LC_CTYPE='C' TEMPLATE=template0 OWNER synapse_user;"The collation is not a detail. Synapse refuses to start on a database created with any collation other than C, and the error it prints when you get this wrong arrives after you have already imported data. Do it now, correctly, once.
2. Homeserver configuration
Debian’s package asks for the server name during installation; answer example.com, not the hostname of the machine. That answer becomes part of every user id and room id on the server and cannot be changed afterwards without abandoning the server.
Then edit /etc/matrix-synapse/homeserver.yaml:
server_name: "example.com"
public_baseurl: "https://matrix.example.com/"
pid_file: /run/matrix-synapse.pid
listeners:
- port: 8008
tls: false
type: http
x_forwarded: true
bind_addresses: ['127.0.0.1']
resources:
- names: [client, federation]
compress: false
database:
name: psycopg2
args:
user: synapse_user
password: "<a long password>"
database: synapse
host: 127.0.0.1
cp_min: 5
cp_max: 10
enable_registration: false
registration_shared_secret: "<a long random string>"
report_stats: false
suppress_key_server_warning: true
url_preview_enabled: false
media_store_path: /var/lib/matrix-synapse/media
max_upload_size: 100M
media_retention:
remote_media_lifetime: 30d
rc_message:
per_second: 0.5
burst_count: 15Two of those settings will save you disk and grief. Remote media retention discards cached copies of other servers’ files after a month, and without it your media store grows forever with content you never asked for. URL previews are off because they make your server fetch arbitrary addresses on behalf of anyone who can post a link, which is a request forgery engine with a chat interface attached.
x_forwarded: true matters as well: without it Synapse rate-limits every user in the world as though they were one client, because every request appears to come from nginx.
3. Delegation
Your identity is example.com and your server is at matrix.example.com. Two files bridge the gap. Serve them from example.com, over HTTPS, with the right content type.
/.well-known/matrix/server:
{ "m.server": "matrix.example.com:443" }/.well-known/matrix/client:
{ "m.homeserver": { "base_url": "https://matrix.example.com" } }The port in the first file is mandatory. Omit it and other servers fall back to port 8448, find nothing listening, and give up quietly; your users then report that federation is broken while every log on your machine looks perfectly healthy.
server {
listen 443 ssl;
server_name example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
location /.well-known/matrix/ {
root /var/www/wellknown;
default_type application/json;
add_header Access-Control-Allow-Origin *;
}
}
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name matrix.example.com;
ssl_certificate /etc/letsencrypt/live/matrix.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/matrix.example.com/privkey.pem;
client_max_body_size 100m;
location ~ ^(/_matrix|/_synapse/client) {
proxy_pass http://127.0.0.1:8008;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $host;
}
}The Access-Control-Allow-Origin header on the well-known location is required by web clients and forgotten by roughly everybody. Without it, desktop clients work and browser clients cannot find your server at all.
mkdir -p /var/www/wellknown/.well-known/matrix
systemctl restart matrix-synapse nginx4. A user
register_new_matrix_user -c /etc/matrix-synapse/homeserver.yaml http://127.0.0.1:8008Answer the prompts, make the first account an admin, and leave open registration off. A homeserver with open registration is a spam source within days, and other servers will start refusing your traffic long before you notice.
Verify it
Work outwards. First, is the software answering at all:
curl -s https://matrix.example.com/_matrix/client/versions | head -c 200
curl -s https://matrix.example.com/_matrix/federation/v1/versionThen, is delegation being served correctly, with the port present and the content type right:
curl -si https://example.com/.well-known/matrix/server | grep -E "content-type|m.server"
curl -s https://example.com/.well-known/matrix/clientThen, does your signing key resolve. Other servers fetch this before they will talk to you at all:
curl -s https://matrix.example.com/_matrix/key/v2/server | head -c 300Finally, the only test that counts. Log in with a client, join a public room hosted on a homeserver that is not yours, and send a message. Then read the federation state from the admin API, using the access token your client shows in its settings:
curl -s -H "Authorization: Bearer <admin token>" \
"https://matrix.example.com/_synapse/admin/v1/federation/destinations" | head -c 400Each destination should show a recent successful transaction and no retry interval. Destinations with a growing retry_interval are servers you cannot reach; if every destination looks like that, the problem is delegation rather than the network, and step three is where to look.
journalctl -u matrix-synapse --since "10 min ago" | grep -i "federation" | tail -20Afterwards
Joining a very large room the first time will pin a core for several minutes and pull in a great deal of state. That is normal, it happens once per room, and it is the main reason people conclude Synapse is slow. Voice and video need a TURN server alongside this one; coturn on the same instance handles a small community, though it wants its own UDP port range in the firewall. Federation is chatty in both directions, which is worth remembering when picking the site: our location index lists the round-trip times we measure ourselves.