2026-04-06 20:29:03 -05:00
2026-04-06 20:25:40 -05:00
2026-04-06 20:25:40 -05:00
2026-04-06 20:25:40 -05:00
2026-04-06 20:25:40 -05:00
2026-04-06 20:29:03 -05:00

Matrix / Element Self-Hosted Server

A Docker Compose setup for running a self-hosted Matrix homeserver (Synapse) with the Element Web client and a Synapse admin panel.

What's included

Service Image Default Port Purpose
Synapse matrixdotorg/synapse:latest 8008 Matrix homeserver
Element Web vectorim/element-web:latest 8080 Web chat client
Synapse Admin awesometechnologies/synapse-admin:latest 8081 Admin UI
PostgreSQL postgres:14 5432 Database for Synapse

Prerequisites

  • Docker and Docker Compose
  • A domain name pointed at your server (e.g. chat.example.com)
  • A reverse proxy (nginx, Caddy, Traefik, etc.) to handle TLS termination — the containers themselves do not manage HTTPS

Setup

1. Clone the repository

git clone <your-repo-url>
cd matrix

2. Configure environment variables

Copy the example env file and edit it:

cp .env.example .env

Open .env and set each value:

# PostgreSQL
POSTGRES_DB=synapse
POSTGRES_USER=synapse
POSTGRES_PASSWORD=<strong-random-password>

# Synapse — must match your public domain
SYNAPSE_SERVER_NAME=chat.example.com

# Synapse Admin UI
REACT_APP_SERVER=https://chat.example.com
REACT_APP_REGISTRATION_ENABLED=false   # set to true only if you want open registration

Security note: Use a strong, unique password for POSTGRES_PASSWORD. Never commit .env to version control — it is already in .gitignore.

3. Generate the Synapse configuration

Synapse needs a homeserver.yaml generated before it can start. Run this once:

docker run --rm \
  -e SYNAPSE_SERVER_NAME=chat.example.com \
  -e SYNAPSE_REPORT_STATS=yes \
  -v "$(pwd)/synapse-data:/data" \
  matrixdotorg/synapse:latest generate

Replace chat.example.com with your actual domain. This creates synapse-data/homeserver.yaml.

4. Point Synapse at PostgreSQL

Open synapse-data/homeserver.yaml and replace the default SQLite database block with:

database:
  name: psycopg2
  args:
    user: synapse
    password: <your-POSTGRES_PASSWORD>
    database: synapse
    host: postgres
    cp_min: 5
    cp_max: 10

Use the same credentials you set in .env.

5. Configure the Element Web client

Copy the example config and edit it:

cp element-config.json.example element-config.json

Update every occurrence of chat.example.com to your domain:

{
  "default_server_config": {
    "m.homeserver": {
      "base_url": "https://chat.example.com",
      "server_name": "chat.example.com"
    }
  },
  "disable_custom_urls": true,
  "brand": "My Matrix Server",
  "showLabsSettings": true,
  "voip": {
    "stun_servers": [
      { "urls": ["stun:turn.example.com:3478"] }
    ],
    "turn_servers": [
      {
        "urls": [
          "turn:turn.example.com:3478?transport=udp",
          "turn:turn.example.com:3478?transport=tcp",
          "turns:turn.example.com:5349?transport=tcp"
        ],
        "secret": "<your-turn-server-secret>",
        "expiry": 86400000
      }
    ]
  }
}

If you do not have a TURN server, remove the voip and webrtc blocks entirely. Voice/video calls on the same local network will still work without them.

6. Start the stack

docker compose up -d

Verify all containers are running:

docker compose ps

Check Synapse logs for errors:

docker compose logs -f synapse

7. Create your first admin user

Once Synapse is running, register an admin account:

docker compose exec synapse register_new_matrix_user \
  -c /data/homeserver.yaml \
  -u <username> \
  -p <password> \
  -a \
  http://localhost:8008

The -a flag grants admin privileges. You can omit it for regular users.


Reverse proxy / TLS

The containers expose plain HTTP. You must front them with a reverse proxy that terminates TLS. A minimal nginx example:

server {
    listen 443 ssl;
    server_name chat.example.com;

    ssl_certificate     /etc/letsencrypt/live/chat.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/chat.example.com/privkey.pem;

    # Element Web
    location / {
        proxy_pass http://127.0.0.1:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }

    # Synapse Matrix API and federation
    location /_matrix {
        proxy_pass http://127.0.0.1:8008;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        client_max_body_size 50M;
    }

    location /_synapse {
        proxy_pass http://127.0.0.1:8008;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

For automatic TLS with Let's Encrypt, use Certbot or Caddy.

Matrix federation (optional)

If you want other Matrix homeservers to be able to communicate with yours, port 8448 must be reachable publicly. You can either:

  • Add a second server { listen 8448 ssl; ... } block that proxies to http://127.0.0.1:8008, or
  • Add a .well-known/matrix/server file served from your domain pointing federation to port 443

Accessing the services

URL Service
https://chat.example.com Element Web client
https://chat.example.com:8081 Synapse Admin panel
https://chat.example.com/_matrix Matrix homeserver API

Log into the admin panel at port 8081 using the admin credentials you created in step 7.


Data persistence

All persistent data is stored in local directories that are bind-mounted into the containers:

Directory Contents
postgres-data/ PostgreSQL database files
synapse-data/ Synapse config, media uploads, signing keys

Both directories are excluded from git via .gitignore. Back them up regularly.


Upgrading

Pull the latest images and recreate the containers:

docker compose pull
docker compose up -d

Check the Synapse changelog before upgrading major versions — some releases require manual migration steps.


Troubleshooting

Synapse fails to start with a database error Make sure the credentials in synapse-data/homeserver.yaml match those in .env, and that the postgres service is fully healthy before Synapse starts. You can force the order with:

docker compose up -d postgres
# wait a few seconds, then:
docker compose up -d synapse element-web element-admin

Element Web shows "Homeserver is not reachable" Verify that base_url in element-config.json uses https:// and points to the domain your reverse proxy serves (not localhost).

"M_FORBIDDEN" when registering users Open registration is disabled by default. Either use the register_new_matrix_user command (step 7) or set enable_registration: true in homeserver.yaml and restart Synapse.

Port conflicts If any default port is already in use on your host, change the left side of the port mapping in docker-compose.yml (e.g. "8082:80" for Element Web) and update your reverse proxy accordingly.

Description
No description provided
Readme 29 KiB