239 lines
7.4 KiB
Markdown
239 lines
7.4 KiB
Markdown
|
|
# Matrix Self-Hosted Stack — Setup Guide
|
|||
|
|
|
|||
|
|
## File Overview
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
matrix-stack/
|
|||
|
|
├── docker-compose.yml # All services: Continuwuity, Traefik, Coturn, LiveKit, lk-jwt-service
|
|||
|
|
├── continuwuity.toml # Homeserver config: TURN URIs, LiveKit foci
|
|||
|
|
├── livekit.yaml # LiveKit media server config
|
|||
|
|
├── coturn.conf # TURN/STUN server config
|
|||
|
|
└── README.md # This file
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Prerequisites
|
|||
|
|
|
|||
|
|
1. Two DNS A records pointing to your server's public IP:
|
|||
|
|
- `matrix.example.com`
|
|||
|
|
- `livekit.example.com`
|
|||
|
|
(If you want your Matrix IDs as `@user:example.com` rather than
|
|||
|
|
`@user:matrix.example.com`, also point `example.com` to your server.)
|
|||
|
|
|
|||
|
|
2. Docker + Docker Compose installed.
|
|||
|
|
|
|||
|
|
3. Create the external proxy network Traefik uses:
|
|||
|
|
```bash
|
|||
|
|
docker network create proxy
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## One-Time Secret Generation
|
|||
|
|
|
|||
|
|
### Coturn secret
|
|||
|
|
```bash
|
|||
|
|
# Install pwgen if needed: apt install pwgen
|
|||
|
|
pwgen -s 64 1
|
|||
|
|
```
|
|||
|
|
Paste the output into both `coturn.conf` (static-auth-secret)
|
|||
|
|
and `continuwuity.toml` (turn_secret). They MUST match.
|
|||
|
|
|
|||
|
|
### LiveKit keys
|
|||
|
|
```bash
|
|||
|
|
docker run --rm livekit/livekit-server:latest generate-keys
|
|||
|
|
```
|
|||
|
|
This outputs a key (~20 chars) and secret (~64 chars).
|
|||
|
|
Paste them into:
|
|||
|
|
- `livekit.yaml` → keys section
|
|||
|
|
- `docker-compose.yml` → lk-jwt-service LIVEKIT_KEY / LIVEKIT_SECRET
|
|||
|
|
|
|||
|
|
### Registration token (for invite-only signup)
|
|||
|
|
```bash
|
|||
|
|
pwgen -s 32 1
|
|||
|
|
```
|
|||
|
|
Paste into docker-compose.yml → CONTINUWUITY_REGISTRATION_TOKEN.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Starting Up
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
# First boot — creates admin user automatically
|
|||
|
|
docker compose up -d
|
|||
|
|
|
|||
|
|
# Get your admin password
|
|||
|
|
docker compose logs homeserver | grep "Created user"
|
|||
|
|
|
|||
|
|
# IMPORTANT: Edit docker-compose.yml and remove the --execute flag
|
|||
|
|
# from the homeserver command, then restart:
|
|||
|
|
docker compose up -d homeserver
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Log in with any Matrix client (Element, Cinny, etc.) using
|
|||
|
|
`@admin:example.com` and the generated password.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Firewall Rules
|
|||
|
|
|
|||
|
|
Open these ports on your server's firewall (ufw examples shown).
|
|||
|
|
|
|||
|
|
### HTTP/HTTPS — handled by Traefik
|
|||
|
|
```bash
|
|||
|
|
ufw allow 80/tcp # HTTP (redirected to HTTPS by Traefik)
|
|||
|
|
ufw allow 443/tcp # HTTPS for matrix.example.com and livekit.example.com
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Coturn — TURN/STUN (raw UDP/TCP, NOT proxied by Traefik)
|
|||
|
|
```bash
|
|||
|
|
ufw allow 3478/tcp # STUN + TURN
|
|||
|
|
ufw allow 3478/udp
|
|||
|
|
ufw allow 5349/tcp # TURN over TLS (if you configured TLS in coturn.conf)
|
|||
|
|
ufw allow 5349/udp
|
|||
|
|
ufw allow 50201:65535/udp # Coturn media relay port range
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### LiveKit — RTC media (raw UDP/TCP, NOT proxied by Traefik)
|
|||
|
|
```bash
|
|||
|
|
ufw allow 7881/tcp # LiveKit direct TCP (for clients that can't UDP)
|
|||
|
|
ufw allow 50100:50200/udp # LiveKit media relay port range
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Summary table
|
|||
|
|
|
|||
|
|
| Port(s) | Protocol | Service | Via Traefik? |
|
|||
|
|
|----------------|-----------|--------------------|--------------|
|
|||
|
|
| 80 | TCP | HTTP (→ HTTPS) | Yes |
|
|||
|
|
| 443 | TCP | HTTPS | Yes |
|
|||
|
|
| 3478 | TCP + UDP | Coturn TURN/STUN | No — direct |
|
|||
|
|
| 5349 | TCP + UDP | Coturn TURN TLS | No — direct |
|
|||
|
|
| 7881 | TCP | LiveKit direct TCP | No — direct |
|
|||
|
|
| 50100–50200 | UDP | LiveKit media | No — direct |
|
|||
|
|
| 50201–65535 | UDP | Coturn media relay | No — direct |
|
|||
|
|
|
|||
|
|
**Key point**: Traefik only handles ports 80 and 443. The Matrix
|
|||
|
|
homeserver (6167) and LiveKit HTTP (7880) are never exposed directly
|
|||
|
|
— Traefik proxies them internally. Coturn and LiveKit's RTC ports
|
|||
|
|
bypass Traefik entirely and are opened directly to the internet.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Can I use an External NGINX Proxy Manager instead of Traefik?
|
|||
|
|
|
|||
|
|
**Short answer: yes, but with important caveats.**
|
|||
|
|
|
|||
|
|
### What NPM can handle
|
|||
|
|
- `matrix.example.com` → proxy to `http://YOUR_SERVER_IP:6167`
|
|||
|
|
- Enable WebSockets
|
|||
|
|
- Set `X-Forwarded-For`, `Host`, `X-Real-IP` headers
|
|||
|
|
- `livekit.example.com` with path splitting:
|
|||
|
|
- `/sfu/get`, `/healthz`, `/get_token` → `http://YOUR_SERVER_IP:8081`
|
|||
|
|
- Everything else → `http://YOUR_SERVER_IP:7880`
|
|||
|
|
- Enable WebSockets on both
|
|||
|
|
- NPM handles Let's Encrypt TLS automatically
|
|||
|
|
|
|||
|
|
### What NPM cannot handle
|
|||
|
|
- **Coturn** — raw UDP/TCP, not HTTP. NPM (like Traefik) can't proxy it.
|
|||
|
|
You just open ports 3478/5349 directly and point DNS at your server IP.
|
|||
|
|
- **LiveKit RTC ports** (7881/tcp, 50100-50200/udp) — same story.
|
|||
|
|
These bypass any reverse proxy entirely.
|
|||
|
|
|
|||
|
|
### Changes needed to docker-compose.yml for NPM
|
|||
|
|
|
|||
|
|
1. **Remove the entire `traefik:` service block.**
|
|||
|
|
|
|||
|
|
2. **Remove all `labels:` blocks** from homeserver and lk-jwt-service.
|
|||
|
|
|
|||
|
|
3. **Expose the ports NPM needs to reach:**
|
|||
|
|
```yaml
|
|||
|
|
homeserver:
|
|||
|
|
ports:
|
|||
|
|
- "127.0.0.1:6167:6167" # NPM proxies this
|
|||
|
|
|
|||
|
|
lk-jwt-service:
|
|||
|
|
ports:
|
|||
|
|
- "127.0.0.1:8081:8081" # NPM proxies this
|
|||
|
|
|
|||
|
|
# livekit already uses network_mode: host, so 7880 is available
|
|||
|
|
# on the host at 127.0.0.1:7880 automatically
|
|||
|
|
```
|
|||
|
|
Using `127.0.0.1:` prefix means only NPM (on the same host or
|
|||
|
|
your network) can reach them — not the open internet.
|
|||
|
|
|
|||
|
|
4. **Remove the `acme:` volume** (NPM handles TLS).
|
|||
|
|
|
|||
|
|
5. In NPM, configure two proxy hosts:
|
|||
|
|
|
|||
|
|
**matrix.example.com**
|
|||
|
|
- Forward to: `http://YOUR_SERVER_IP:6167`
|
|||
|
|
- Websockets: ON
|
|||
|
|
- Custom nginx config:
|
|||
|
|
```nginx
|
|||
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|||
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|||
|
|
proxy_set_header Host $http_host;
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**livekit.example.com**
|
|||
|
|
- Forward to: `http://YOUR_SERVER_IP:7880` (default)
|
|||
|
|
- Websockets: ON
|
|||
|
|
- Advanced tab — add this custom config for path splitting:
|
|||
|
|
```nginx
|
|||
|
|
location ~ ^/(sfu/get|healthz|get_token) {
|
|||
|
|
proxy_pass http://YOUR_SERVER_IP:8081$request_uri;
|
|||
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|||
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|||
|
|
proxy_set_header Host $http_host;
|
|||
|
|
proxy_buffering off;
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### NPM on a separate machine?
|
|||
|
|
If NPM runs on a different server, replace `127.0.0.1` with your
|
|||
|
|
Matrix server's internal IP, and make sure port 6167 is firewalled
|
|||
|
|
to only allow connections from NPM's IP.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Verification
|
|||
|
|
|
|||
|
|
### Test TURN credentials
|
|||
|
|
```bash
|
|||
|
|
curl "https://matrix.example.com/_matrix/client/r0/voip/turnServer" \
|
|||
|
|
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" | jq
|
|||
|
|
```
|
|||
|
|
Should return a JSON object with `uris`, `username`, `password`.
|
|||
|
|
|
|||
|
|
### Test TURN connectivity
|
|||
|
|
1. Go to https://webrtc.github.io/samples/src/content/peerconnection/trickle-ice/
|
|||
|
|
2. Paste the credentials from above
|
|||
|
|
3. Click "Gather candidates" — look for `relay` type candidates
|
|||
|
|
|
|||
|
|
### Test LiveKit
|
|||
|
|
1. GET `https://livekit.example.com/healthz` — should return 200
|
|||
|
|
2. Use https://livekit.io/connection-test with a token from `/get_token`
|
|||
|
|
|
|||
|
|
### Test federation
|
|||
|
|
https://federationtester.matrix.org — enter your domain
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Troubleshooting
|
|||
|
|
|
|||
|
|
**Federation not working**
|
|||
|
|
- Check `.well-known/matrix/server` is reachable:
|
|||
|
|
`curl https://example.com/.well-known/matrix/server`
|
|||
|
|
- Should return: `{"m.server":"matrix.example.com:443"}`
|
|||
|
|
|
|||
|
|
**TURN not working**
|
|||
|
|
- Verify firewall allows 3478/udp from the internet
|
|||
|
|
- Check coturn logs: `docker compose logs coturn`
|
|||
|
|
- Confirm `turn_secret` in continuwuity.toml matches `static-auth-secret` in coturn.conf
|
|||
|
|
|
|||
|
|
**Element Call / group calls failing**
|
|||
|
|
- Check lk-jwt-service is reachable: `curl https://livekit.example.com/healthz`
|
|||
|
|
- Confirm LiveKit UDP ports (50100-50200) are open
|
|||
|
|
- Check `foci` URL in continuwuity.toml matches where lk-jwt-service is deployed
|