Matrix is an open, decentralised protocol for chat, voice and video, with end-to-end encryption. Anyone can run a homeserver, and homeservers federate: users on your server can talk to users on matrix.org or any other Matrix server, much like email. Synapse is the reference homeserver implementation. Running your own gives you control over your team’s or community’s data, identities and retention.

Before you start

  • Your Matrix ID will be @user:example.com. The part after : (the server name) is permanent and can’t be changed later. You can host the server on a subdomain (matrix.example.com) while users get IDs on your main domain via delegation.
  • Resources: a small private server (a handful of users, few large rooms) runs on 2 vCPU and 2–4 GB RAM. Joining big public rooms over federation uses much more CPU, RAM and disk.
  • Use PostgreSQL (SQLite is only for testing).

Alternatives: Conduit/conduwuit (lightweight, Rust) and Dendrite. Synapse is the most feature-complete.

Docker Compose

# /opt/matrix/compose.yaml
services:
  synapse:
    image: matrixdotorg/synapse:latest
    restart: unless-stopped
    volumes:
      - ./synapse:/data
    ports:
      - "127.0.0.1:8008:8008"
    depends_on: [db]

  db:
    image: postgres:16
    restart: unless-stopped
    environment:
      POSTGRES_USER: synapse
      POSTGRES_PASSWORD: change-me-db
      POSTGRES_INITDB_ARGS: "--encoding=UTF8 --lc-collate=C --lc-ctype=C"
    volumes:
      - ./postgres:/var/lib/postgresql/data

Generate the config for your server name:

cd /opt/matrix
docker run -it --rm -v $PWD/synapse:/data -e SYNAPSE_SERVER_NAME=example.com -e SYNAPSE_REPORT_STATS=no matrixdotorg/synapse:latest generate

Edit synapse/homeserver.yaml to use PostgreSQL (note the C collation above, which Synapse requires):

database:
  name: psycopg2
  args:
    user: synapse
    password: change-me-db
    database: synapse
    host: db
    cp_min: 5
    cp_max: 10

enable_registration: false
public_baseurl: https://matrix.example.com/

Start it with docker compose up -d, and check docker compose logs -f synapse.

Reverse proxy and federation

Clients and other servers connect over HTTPS. Federation uses port 8448 by default, or 443 with delegation. With Nginx:

server {
    server_name matrix.example.com;
    listen 443 ssl;
    listen 8448 ssl;                       # federation (optional with delegation)
    client_max_body_size 50M;
    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;
    }
    # ssl_certificate … (certbot)
}

Don’t proxy /_synapse/admin publicly. Use it via an SSH tunnel or an allow-list.

Delegation: IDs on example.com, server on matrix.example.com

Serve two small JSON files on https://example.com/.well-known/matrix/:

/.well-known/matrix/server  →  {"m.server": "matrix.example.com:443"}
/.well-known/matrix/client  →  {"m.homeserver": {"base_url": "https://matrix.example.com"}}

(Serve client with Access-Control-Allow-Origin: *.) Then federation works over port 443, and port 8448 isn’t needed.

Check your setup with the Matrix Federation Tester (federationtester.matrix.org).

Create users

With registration disabled, create accounts from the server:

docker compose exec synapse register_new_matrix_user -c /data/homeserver.yaml http://localhost:8008

Answer the prompts, and make yourself an admin. For teams, connect SSO (OIDC with Keycloak or Authentik), or use registration tokens instead of open registration. Open registration attracts spam bots.

Clients

Element (web, desktop, iOS, Android) is the flagship client. Log in with your homeserver URL. You can host Element Web yourself (static files behind Nginx). Other clients: FluffyChat, Cinny, Nheko.

Set up key backup (recovery key or passphrase) in the client, so encrypted message history survives device loss.

Voice and video: TURN

1:1 calls behind NAT need a TURN server (coturn) configured in Synapse (turn_uris, turn_shared_secret). Group calls use Element Call or Jitsi. Open TURN’s ports (UDP 3478 plus a relay port range) in the firewall.

Maintenance

  • Disk: media and room state grow over time. Configure media_retention, purge old remote media via the admin API, and avoid joining huge public rooms if resources are tight.
  • Backups: PostgreSQL dumps, plus synapse/ (config, signing key, media store). Losing the signing key breaks federation trust for your server name. See backup strategy.
  • Updates: Synapse releases often. Read the upgrade notes, and update regularly. See updating self-hosted apps.

Quick reference

Item Value
Client and federation API /_matrix via HTTPS
Synapse port (internal) 8008
Federation port 8448, or 443 with .well-known delegation
Create a user register_new_matrix_user -c /data/homeserver.yaml …
Test federation federationtester.matrix.org

Related: Mattermost and Rocket.Chat.

  • Self-Hosting
  • Chat
  • Docker