Workload · Running your own Matrix chat

A self-hosted Matrix server your team's chat can live on

To self-host a Matrix server you run Synapse, the homeserver software maintained by Element, with a PostgreSQL database, behind a web server that handles HTTPS, and you tell other Matrix servers where to find it. Your people then sign in from Element or any Matrix app as @name:yourdomain, and can still talk to anyone on other Matrix servers. Synapse's documentation wants a spare gigabyte of memory before you join big public rooms, and warns that the default SQLite database is only for testing. Below, the whole thing runs in Docker on a Cloud VPS.

The short version
  1. Your domain in every address: @you:example.com
  2. Docker Compose: Synapse plus PostgreSQL in two services
  3. Caddy gets the certificates and tells other servers where you are
  4. Nobody can sign up unless you let them
  5. Shared photos and files pile up unless you set a clean-up rule

Decide your address before you install

Your Matrix address is the one setting you cannot undo. Synapse calls it the server_name, and it becomes the end of every username and room alias, like @sam:example.com. The docs are blunt: it cannot be changed later. Most people want their plain domain here, the way their email reads, even if Synapse itself runs at matrix.example.com. That split is normal and takes one small file, covered further down.

How big a server does Matrix need?

Less than you might fear for a small group, more than you would guess if you join busy public rooms. Synapse keeps recent conversations in memory to stay quick, and its docs give one firm number: a gigabyte of memory left free for taking part in big public rooms like #matrix:matrix.org. No official figure exists per user, because the work depends on the rooms. A room shared with hundreds of other servers means talking to all of them.

Your situationWhat to watch
A family or small team, mostly private roomsModest memory and disk; that one-gigabyte number leaves headroom
Members who join big public roomsMemory for Synapse's caches, and database growth from those rooms' history
Lots of photos, voice notes and filesDisk: every upload is stored, and media from other servers is copied in
Video and voice callsA separate TURN server, which you add yourself

The other thing to settle early is the database. Synapse starts on SQLite, but its installation guide rules SQLite out for real use, saying it struggles most in big rooms. Start on PostgreSQL and you never have to migrate.

Why a Cloud VPS suits a small homeserver

A chat server for a few dozen people is a small, steady job, which is exactly what our Cloud VPS is for. You get root access, your own IPv4 address and an IPv6 /96, a console in your browser for the day SSH will not answer, and reinstalls from your dashboard. It runs in New York, and it is usually built within a couple of minutes of payment.

  • Traffic in is free. Only what your server sends counts toward the size's monthly traffic. Media pulled from other servers costs you nothing.
  • No cliff at the limit. If the month's traffic runs out, sending slows to 10 Mbit/s until your next month starts, and the server keeps running.
  • Room to grow. Ask us in live chat and your machine moves to a bigger size, restarting once and keeping its files and addresses. Sizes only go up, because a disk cannot be made smaller.
  • Docker ready. Pick the free Docker app when you order and the machine arrives with Docker and Docker Compose installed.

Up and running with Docker Compose

  1. Choose the Docker app when you order, add your SSH key, and point DNS records for matrix.example.com at the machine's address. If the VPS will also serve the .well-known files (the example.com block in the Caddyfile below), point example.com at it as well. Otherwise serve those two files from wherever example.com already lives.
  2. Make a folder for the server and save the compose file below in it. It is a trimmed copy of the example Synapse publishes, with Synapse listening only on the machine itself. Change the database password.
  3. Generate the configuration and signing keys with the generate command below, using your chosen address as the server name.
  4. Open files/homeserver.yaml. Replace the SQLite database section with PostgreSQL: name: psycopg2, and under args the user synapse, your password, dbname: synapse and host: db. On the 8008 listener, set x_forwarded: true. Set public_baseurl to https://matrix.example.com.
  5. Start it with docker compose up -d, then check docker compose logs synapse for errors.
services:
  synapse:
    image: docker.io/matrixdotorg/synapse:latest
    restart: unless-stopped
    environment:
      - SYNAPSE_CONFIG_PATH=/data/homeserver.yaml
    volumes:
      - ./files:/data
    depends_on:
      - db
    ports:
      - 127.0.0.1:8008:8008/tcp

  db:
    image: docker.io/postgres:15-alpine
    restart: unless-stopped
    environment:
      - POSTGRES_USER=synapse
      - POSTGRES_PASSWORD=changeme
      - POSTGRES_INITDB_ARGS=--encoding=UTF-8 --lc-collate=C --lc-ctype=C
    volumes:
      - ./schemas:/var/lib/postgresql/data

# generate the config once, before the first start:
# docker compose run --rm -e SYNAPSE_SERVER_NAME=example.com -e SYNAPSE_REPORT_STATS=no synapse generate

The POSTGRES_INITDB_ARGS line matters: Synapse needs a UTF-8 database with C collation, and it has to be right when the database is first created. Inside the container, leave Synapse's bind_addresses alone; binding it to 127.0.0.1 there stops the proxy reaching it. The 127.0.0.1 in the compose file does that job from outside.

HTTPS, and letting other servers find you

By default, other Matrix servers look for yours at your address on port 8448. If your address is example.com but Synapse lives at matrix.example.com, you publish a small file under /.well-known/matrix/server on example.com pointing them to matrix.example.com:443. That is called delegation, and it means port 8448 never has to be open. A second file, /.well-known/matrix/client, lets apps find the server when someone types just @sam:example.com.

Caddy, one of the proxies the Synapse docs give examples for, does both jobs and gets the certificates for you. Install it on the VPS from Caddy's own package repository and use a Caddyfile like this one, taken from the Synapse reverse proxy guide:

example.com {
	header /.well-known/matrix/* Content-Type application/json
	header /.well-known/matrix/* Access-Control-Allow-Origin *
	respond /.well-known/matrix/server `{"m.server": "matrix.example.com:443"}`
	respond /.well-known/matrix/client `{"m.homeserver":{"base_url":"https://matrix.example.com"}}`
}

matrix.example.com {
	reverse_proxy /_matrix/* localhost:8008
	reverse_proxy /_synapse/client/* localhost:8008
}

If your main website for example.com lives somewhere else, serve the two .well-known files from that website instead and keep only the matrix.example.com block here. Then run the Matrix federation tester against your domain. One trap from the docs: 308 redirects are not followed by Synapse, so a 308 from example.com to www.example.com breaks federation.

Keep sign-ups closed and add people yourself

Registration is off unless you switch it on (enable_registration defaults to false), and for a private server that is how it should stay. To add people, put a registration_shared_secret in homeserver.yaml, restart, and run docker exec -it with the container's name and register_new_matrix_user http://localhost:8008 -c /data/homeserver.yaml. It asks for a username, a password and whether the account is an admin. Treat that secret like a root password: it creates accounts even with registration off. The Docker guide suggests removing it again once you are done.

Want people to sign themselves up? Turn on registration_requires_token and hand out invite tokens from the admin API, or require a captcha or an email address. The docs describe open sign-ups without any check as a known route for spam and abuse.

Give Synapse an email account too, through a mail service on port 587, or password resets will not work. On a new VPS, port 25 stays shut until you ask, and 465 and 587 work from day one.

Stop the disk from filling up quietly

Every photo shared in a room is stored, and media from other servers is copied onto yours when someone views it. Without a rule, Synapse keeps all of it forever. Add a media_retention block to homeserver.yaml: remote_media_lifetime: 14d clears copies of other servers' media nobody has opened for two weeks, and local_media_lifetime does the same for your own members' uploads, if you want that.

  • Uploads larger than max_upload_size (50M by default) are refused.
  • Link previews are off by default. If you enable them, you must list the private address ranges Synapse may not fetch.
  • Back up the files folder (configuration, signing key, media) and a pg_dump of the database. Before moving to a new major PostgreSQL version, stop Synapse and back up first.

When you'll want a whole machine

A VPS shares its processor time with other customers' machines and caps its disk speed. If your community grows into the thousands, or your server sits in many large public rooms and stays busy all day, a dedicated server gives Synapse and PostgreSQL every core and disk with nobody else on it, on a port nobody counts, in New York and our other locations. Planning face-to-face calls as well? A Jitsi Meet server of your own has different needs from chat.

Questions people ask

Do I need my own domain name to run Matrix?
Yes. Your domain becomes part of everyone's address, and other Matrix servers use it to find yours. It must be chosen before you install, because Synapse cannot change it afterwards.
Can people on my server talk to people on matrix.org?
Yes, once federation is working. Other servers need to reach yours over HTTPS, either on port 8448 or on 443 through the .well-known delegation file. The Matrix federation tester tells you whether they can.
Is SQLite fine for a handful of users?
Not for real use. Synapse's documentation keeps SQLite for testing and says it slows down, above all in big rooms. PostgreSQL in a second container costs little, and moving from SQLite later means running a migration script with the server stopped.
Will voice and video calls work straight away?
Not reliably. Synapse's docs say calls routed through your homeserver need a TURN server. Synapse leaves that out, and so does its Docker image. Plan it as a separate piece.
What happens if my server runs out of monthly traffic?
It keeps running. Only what it sends counts; once the month's traffic is used, sending slows to 10 Mbit/s until your next billing month, while incoming traffic stays at full speed. You can add more traffic from the Virtual Machines tab.

Give your team a chat server of its own

A Cloud VPS in New York with root access, the free Docker app, and room to move up a size when your community grows.

Get a server for your Matrix chat

Read next