# Matrix / Element Self-Hosted Server A Docker Compose setup for running a self-hosted [Matrix](https://matrix.org/) homeserver (Synapse) with the [Element Web](https://element.io/) 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](https://docs.docker.com/get-docker/) and [Docker Compose](https://docs.docker.com/compose/install/) - 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 ```bash git clone cd matrix ``` ### 2. Configure environment variables Copy the example env file and edit it: ```bash cp .env.example .env ``` Open `.env` and set each value: ```dotenv # PostgreSQL POSTGRES_DB=synapse POSTGRES_USER=synapse POSTGRES_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: ```bash 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: ```yaml database: name: psycopg2 args: user: synapse 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: ```bash cp element-config.json.example element-config.json ``` Update every occurrence of `chat.example.com` to your domain: ```json { "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": "", "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 ```bash docker compose up -d ``` Verify all containers are running: ```bash docker compose ps ``` Check Synapse logs for errors: ```bash docker compose logs -f synapse ``` ### 7. Create your first admin user Once Synapse is running, register an admin account: ```bash docker compose exec synapse register_new_matrix_user \ -c /data/homeserver.yaml \ -u \ -p \ -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: ```nginx 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](https://certbot.eff.org/) or [Caddy](https://caddyserver.com/). ### 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: ```bash docker compose pull docker compose up -d ``` Check the [Synapse changelog](https://github.com/element-hq/synapse/blob/master/CHANGES.md) 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: ```bash 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.