Matrix Synapse на VPS: полный гайд по self-hosted мессенджеру
Matrix Synapse + Element на VPS: Docker, PostgreSQL, TURN, federation, SSL, E2E encryption, бэкапы и hardening. Альтернатива Slack и Telegram для команд.
Краткий ответ: Matrix — децентрализованный протокол чата с E2E encryption. Synapse — reference homeserver. Stack на VPS 4 GB+: Synapse + PostgreSQL + coturn TURN + Element Web + Nginx SSL + бэкапы.
Slack/Telegram для рабочих переписок — vendor lock-in и чужие серверы. Matrix на вашем VPS = rooms, threads, bridges, federation с другими серверами, полный контроль retention и compliance.
Это максимально подробный практический гайд: от нуля до production federation за один вечер.
Содержание roadmap
- Архитектура и компоненты
- Требования к VPS и sizing
- DNS и подготовка сервера
- PostgreSQL production setup
- Synapse Docker Compose (full)
- coturn TURN для звонков
- Element Web client
- Nginx reverse proxy + SSL
- Регистрация, rooms, spaces
- Federation с другими серверами
- Bridges (Telegram, IRC, Slack)
- E2E encryption и key backup
- SSO через Authentik
- Moderation и admin API
- Backup и disaster recovery
- Monitoring и logs
- Performance tuning
- Security hardening checklist
- Troubleshooting (30+ кейсов)
- Migration из Slack/Telegram
Matrix vs Slack vs Telegram vs Discord
| Критерий | Matrix (Synapse) | Slack | Telegram | Discord |
|---|---|---|---|---|
| Self-hosted | Да | Нет | Нет | Нет |
| E2E default | Rooms optional E2E | Нет | Secret chats only | Нет |
| Federation | Да (server-to-server) | Нет | Нет | Нет |
| Open protocol | Да | Нет | Нет | Нет |
| Bridges | mautrix, many | Limited | Bots | Bots |
| RAM (self-host) | 4 GB+ | N/A | N/A | N/A |
| Mobile apps | Element, FluffyChat | Native | Native | Native |
| Compliance | Your data | US cloud | UAE/RU issues | US cloud |
| Threads | Да (threads) | Да | Topics | Forum channels |
| Voice/Video | Matrix RTC + Element Call | Native | Native | Native |
Matrix сложнее в setup, но единственный enterprise-grade open protocol с federation.
Архитектура production stack
Internet
↓
Nginx (443) + Let's Encrypt
↙ ↘
Element Web Synapse :8008
(static) ↓
PostgreSQL
↓
Media store (/data/media)
↓
coturn TURN (3478) — VoIP relay
↓
Optional: [Authentik SSO](/blog/authentik-sso-vps/)
Federation traffic идёт напрямую server-to-server на :8448 (или через 443 delegated SRV).
Компоненты и роли
| Компонент | Назначение | Обязателен? |
|---|---|---|
| Synapse | Homeserver, rooms, events | Да |
| PostgreSQL | Event store, state | Да (не SQLite в prod) |
| coturn | TURN/STUN для звонков за NAT | Для VoIP — да |
| Element Web | Web client | Рекомендуется |
| Redis | Cache (optional Synapse 1.x+) | 50+ users |
| Synapse Admin API | User/room management | Admin |
| Maubot / bridges | Telegram, Slack bridge | Optional |
Требования к VPS
| Масштаб | Users | RAM | CPU | Disk | Bandwidth |
|---|---|---|---|---|---|
| Personal / семья | 5–20 | 2 GB | 2 vCPU | 20 GB SSD | 100 GB/mo |
| Малый team | 20–100 | 4 GB | 2–4 vCPU | 50 GB SSD | 500 GB/mo |
| Средний | 100–500 | 8 GB | 4 vCPU | 100 GB NVMe | 1 TB/mo |
| Large | 500+ | 16 GB+ | 8 vCPU | 200 GB+ NVMe | Dedicated |
Не ставьте Synapse на 1 GB RAM — OOM при media upload и federation sync.
Disk растёт от media (фото, файлы в rooms). Retention policy критична.
DNS записи (до установки)
Для домена matrix.example.com:
| Тип | Имя | Значение |
|---|---|---|
| A | matrix.example.com | VPS_IP |
| A | turn.example.com | VPS_IP |
| SRV | _matrix._tcp | matrix.example.com:443 (delegation) |
| TXT | _matrix | v=matrix1 (verification) |
Federation port: либо :8448 открыт, либо .well-known delegation на 443:
// https://matrix.example.com/.well-known/matrix/server
{"m.server": "matrix.example.com:443"}
// https://matrix.example.com/.well-known/matrix/client
{"m.homeserver": {"base_url": "https://matrix.example.com"}}
Проверка: curl https://matrix.example.com/.well-known/matrix/server
Подготовка VPS
# Ubuntu 24.04
sudo apt update && sudo apt upgrade -y
sudo apt install -y docker.io docker-compose-plugin nginx certbot python3-certbot-nginx
# Firewall
sudo nft add rule inet filter input tcp dport { 22, 80, 443, 8448, 3478, 5349 } accept
# или см. [nftables гайд](/blog/nftables-firewall-vps/)
Hostname: matrix.example.com. Timezone UTC. Swap 2 GB если RAM 4 GB.
PostgreSQL (отдельный container)
# docker-compose.yml — postgres service
postgres:
image: postgres:16-alpine
restart: unless-stopped
environment:
POSTGRES_USER: synapse
POSTGRES_PASSWORD: STRONG_DB_PASSWORD
POSTGRES_DB: synapse
POSTGRES_INITDB_ARGS: "--encoding=UTF8 --lc-collate=C --lc-ctype=C"
volumes:
- ./postgres-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U synapse"]
interval: 10s
timeout: 5s
retries: 5
Tuning для 4 GB VPS (postgresql.conf overrides):
shared_buffers = 512MB
effective_cache_size = 1536MB
maintenance_work_mem = 128MB
work_mem = 8MB
max_connections = 100
Подробнее — PostgreSQL tuning.
Synapse: генерация config
mkdir -p synapse-data
docker run -it --rm \
-v ./synapse-data:/data \
-e SYNAPSE_SERVER_NAME=matrix.example.com \
-e SYNAPSE_REPORT_STATS=no \
matrixdotorg/synapse:latest generate
Редактируем homeserver.yaml:
server_name: "matrix.example.com"
public_baseurl: "https://matrix.example.com/"
pid_file: /data/homeserver.pid
listeners:
- port: 8008
tls: false
type: http
x_forwarded: true
resources:
- names: [client, federation]
compress: false
database:
name: psycopg2
args:
user: synapse
password: STRONG_DB_PASSWORD
database: synapse
host: postgres
cp_min: 5
cp_max: 10
media_store_path: /data/media
uploads_path: /data/uploads
max_upload_size: 50M
enable_registration: false
enable_registration_without_verification: false
registration_shared_secret: "RANDOM_SECRET_FOR_ADMIN_REGISTER"
# Rate limits
rc_message:
per_second: 0.2
burst_count: 10
# Federation
federation_domain_whitelist: [] # empty = all
allow_public_rooms_without_auth: false
# Email (optional, for password reset)
email:
enable_notifs: true
smtp_host: smtp.example.com
smtp_port: 587
smtp_user: "matrix@example.com"
smtp_pass: "SMTP_PASSWORD"
notif_from: "Matrix <matrix@example.com>"
# TURN
turn_uris:
- "turn:turn.example.com:3478?transport=udp"
- "turn:turn.example.com:3478?transport=tcp"
- "turns:turn.example.com:5349?transport=tcp"
turn_shared_secret: "TURN_SECRET_MATCH_COTURN"
turn_user_lifetime: 86400000
Полный Docker Compose
services:
postgres:
image: postgres:16-alpine
restart: unless-stopped
environment:
POSTGRES_USER: synapse
POSTGRES_PASSWORD: STRONG_DB_PASSWORD
POSTGRES_DB: synapse
volumes:
- ./postgres-data:/var/lib/postgresql/data
networks:
- matrix
synapse:
image: matrixdotorg/synapse:latest
restart: unless-stopped
depends_on:
postgres:
condition: service_healthy
volumes:
- ./synapse-data:/data
ports:
- "127.0.0.1:8008:8008"
networks:
- matrix
coturn:
image: coturn/coturn:latest
restart: unless-stopped
network_mode: host
volumes:
- ./coturn/turnserver.conf:/etc/coturn/turnserver.conf
element:
image: vectorim/element-web:latest
restart: unless-stopped
volumes:
- ./element-config.json:/app/config.json
ports:
- "127.0.0.1:8080:80"
networks:
- matrix
networks:
matrix:
coturn configuration
# turnserver.conf
listening-port=3478
tls-listening-port=5349
fingerprint
use-auth-secret
static-auth-secret=TURN_SECRET_MATCH_COTURN
realm=turn.example.com
total-quota=100
stale-nonce=600
cert=/etc/letsencrypt/live/turn.example.com/fullchain.pem
pkey=/etc/letsencrypt/live/turn.example.com/privkey.pem
no-multicast-peers
no-cli
Без TURN — Element Call и video не работают за symmetric NAT (большинство mobile networks).
Element Web config.json
{
"default_server_config": {
"m.homeserver": {
"base_url": "https://matrix.example.com",
"server_name": "matrix.example.com"
}
},
"brand": "Company Chat",
"disable_guests": true,
"integrations_ui_url": "https://scalar.vector.im/",
"integrations_rest_url": "https://scalar.vector.im/api",
"bug_report_endpoint_url": "https://element.io/bugreports/submit",
"defaultCountryCode": "RU",
"showLabsSettings": true,
"features": {
"feature_video_rooms": true
}
}
Host Element на chat.example.com или element.matrix.example.com.
Nginx configuration (complete)
# matrix.example.com — Synapse
server {
listen 443 ssl http2;
server_name matrix.example.com;
ssl_certificate /etc/letsencrypt/live/matrix.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/matrix.example.com/privkey.pem;
location /.well-known/matrix/server {
default_type application/json;
add_header Access-Control-Allow-Origin *;
return 200 '{"m.server": "matrix.example.com:443"}';
}
location /.well-known/matrix/client {
default_type application/json;
add_header Access-Control-Allow-Origin *;
return 200 '{"m.homeserver":{"base_url":"https://matrix.example.com"}}';
}
location ~* ^(\/_matrix|\/_synapse) {
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;
client_max_body_size 50M;
proxy_read_timeout 600s;
}
}
# chat.example.com — Element Web
server {
listen 443 ssl http2;
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;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
}
}
SSL — Let’s Encrypt или Certbot DNS для wildcard.
Создание первого admin user
# Регистрация через shared secret (enable_registration: false в yaml)
register_new_matrix_user -c synapse-data/homeserver.yaml http://localhost:8008
# Или через Docker:
docker exec -it synapse-synapse-1 register_new_matrix_user \
-c /data/homeserver.yaml http://localhost:8008
Сразу: Settings → Security → Cross-signing + Secure backup для E2E.
Invite-only policy:
enable_registration: false
# Создавайте users через admin API или register CLI
Rooms, Spaces, организация
| Концепция | Аналог Slack | Описание |
|---|---|---|
| Room | Channel | Чат, public/private |
| Space | Workspace section | Группа rooms |
| Thread | Thread | Reply chain |
| DM | DM | 1:1 encrypted |
| Power levels | Permissions | Admin, mod, user |
Space для команды:
- Create Space → Private
- Add rooms: #general, #dev, #ops, #random
- Invite by @user:matrix.example.com
- Pin important messages, enable E2E for sensitive rooms
Public room discovery — отключите в production (allow_public_rooms_without_auth: false).
Federation
Federation позволяет писать пользователям на других серверах: @user:matrix.org, @user:company.com.
Проверка federation:
# С вашего сервера
curl "https://matrix.example.com/_matrix/federation/v1/version"
# External test
# https://federationtester.matrix.org/
Whitelist federation (если только internal):
federation_domain_whitelist:
- matrix.example.com
- partner-company.com
Disable federation completely:
federation_domain_whitelist:
- matrix.example.com # only self
Bridges: Telegram, Slack, IRC
mautrix-telegram — двусторонний bridge Telegram ↔ Matrix.
# Отдельный container mautrix-telegram
# config.yaml: homeserver address, appservice registration
# Регистрация appservice в Synapse: app_service_config_files
Workflow:
- Deploy mautrix bridge container
- Generate registration.yaml
- Add to Synapse
app_service_config_files - Restart Synapse
- Login Telegram bot in bridge DM
!tg login→ link rooms
Аналогично: mautrix-slack, mautrix-discord, matrix-appservice-irc.
Bridges ломают E2E (bridge видит plaintext) — отдельные rooms для bridged content.
E2E Encryption deep dive
| Уровень | Что шифруется | Кто не видит |
|---|---|---|
| TLS transport | Client ↔ Server | Network sniffers |
| E2E room | Messages in room | Server admin |
| DM default | 1:1 messages | Server |
Cross-signing — verify devices, prevent MITM.
Key backup passphrase — храните offline. Без backup — потеря всех E2E keys при смене телефона.
Server admin не может прочитать E2E rooms — это feature, не bug. Support = user exports.
SSO через Authentik (OIDC)
- Authentik → Application → OAuth2/OIDC
- Synapse
homeserver.yaml:
oidc_providers:
- idp_id: authentik
discover: true
issuer: "https://auth.example.com/application/o/matrix/"
client_id: "synapse-client-id"
client_secret: "secret"
scopes: ["openid", "profile", "email"]
user_mapping_provider:
config:
localpart_template: "{{ user.preferred_username }}"
display_name_template: "{{ user.name }}"
- Disable local password login для org users
- Map groups → Synapse roles via claims (advanced)
Moderation и Synapse Admin API
# List users
curl -H "Authorization: Bearer $ADMIN_TOKEN" \
"https://matrix.example.com/_synapse/admin/v2/users?guests=false"
# Deactivate user
curl -X POST -H "Authorization: Bearer $ADMIN_TOKEN" \
"https://matrix.example.com/_synapse/admin/v1/deactivate/@spam:matrix.example.com"
# Purge room
curl -X POST -H "Authorization: Bearer $ADMIN_TOKEN" \
-d '{"block": true, "purge": true}' \
"https://matrix.example.com/_synapse/admin/v1/rooms/!roomid:matrix.example.com/delete"
Admin token: homeserver.yaml → generate via synapse_admin or config.
Moderation policy: power level 50+ for kick, 100 for ban. Audit log через Loki.
Media retention и cleanup
Media занимает 80% disk на активных rooms.
# homeserver.yaml
media_retention:
local_media_lifetime: 90d
remote_media_lifetime: 30d
Cron cleanup:
docker exec synapse-synapse-1 \
synapse_review_media_storage --before-days 90 --remove
Offload старых media в MinIO S3 — advanced (S3 storage provider plugins).
Backup и disaster recovery
Что бэкапить:
| Компонент | Path | Priority |
|---|---|---|
| PostgreSQL | postgres-data/ | Critical |
| Synapse config | synapse-data/homeserver.yaml | Critical |
| Media store | synapse-data/media/ | High |
| Signing key | synapse-data/*.signing.key | Critical |
| Element config | element-config.json | Medium |
# PostgreSQL dump
docker exec postgres pg_dump -U synapse synapse | gzip > synapse-db-$(date +%F).sql.gz
# Full tar
tar czf matrix-backup-$(date +%F).tar.gz postgres-data/ synapse-data/ element-config.json
Strategy 3-2-1 + Restic offsite.
Restore test quarterly. Signing key потерян = новый server_name (катастрофа).
Monitoring
| Metric | Tool | Alert |
|---|---|---|
| Synapse up | Uptime Kuma | HTTP 200 /_matrix/client/versions |
| PostgreSQL connections | Prometheus | > 80 max |
| Disk usage | node_exporter | > 85% |
| Federation errors | Synapse logs | spike in federation failures |
| TURN allocation | coturn logs | failures |
# prometheus scrape synapse metrics (enable in homeserver.yaml)
enable_metrics: true
Logs → Loki + Grafana или OpenSearch.
Performance tuning
| Проблема | Решение |
|---|---|
| Slow message send | PostgreSQL indexes, more RAM, Redis cache |
| Federation lag | Check DNS SRV, port 8448, bandwidth |
| High CPU sync | Limit federation partners, worker processes |
| OOM on startup | Increase RAM, reduce cp_max db pool |
| Slow media upload | SSD disk, increase max_upload_size carefully |
Synapse workers (800+ users): split federation, media, client readers на отдельные processes — см. official docs “Workers”.
Redis cache:
redis:
enabled: true
host: redis
port: 6379
Security hardening checklist
-
enable_registration: false— invite/admin only - Strong admin tokens, rotate quarterly
- CrowdSec + fail2ban
- nftables — minimal ports
- Admin UI/API only via Tailscale
- E2E enabled for confidential rooms
- Federation whitelist if internal-only
- Regular Synapse updates (security patches)
- Audit user list monthly
- Rate limits configured (anti-spam)
- SMTP TLS for password reset emails
- No public room directory
Troubleshooting (extended)
| Симптом | Причина | Fix |
|---|---|---|
| Can’t connect client | DNS/SSL | Check .well-known, cert expiry |
| Federation fails | Port 8448 blocked | Open port or 443 delegation |
| Federation tester red | SRV records | Fix DNS _matrix._tcp |
| Video call no connect | TURN missing | coturn config, turn_uris in yaml |
| Video one-way audio | TURN credentials | turn_shared_secret match |
| “M_UNKNOWN” on register | Registration disabled | Use admin register CLI |
| Database error on start | PostgreSQL down | Check healthcheck, credentials |
| Slow room load | Large state | Purge old rooms, vacuum DB |
| Media 413 | Nginx body size | client_max_body_size 50M |
| Media 404 | Wrong media_store path | Check volume mount |
| E2E can’t decrypt | Missing cross-signing | Re-verify devices, restore key backup |
| Bridge messages duplicate | Double bridge instance | One bridge per account |
| High memory | Media cache | Retention policy, restart |
| SSL mixed content | Element wrong base_url | Fix config.json homeserver URL |
| OIDC login loop | Redirect URI mismatch | Authentik callback URL |
| Spam registrations | Open registration | Disable, add captcha |
| Room invite fails | Blocked federation | Whitelist domain |
| Synapse 502 | Container crash | docker logs, DB connection |
| PostgreSQL disk full | No retention | Vacuum, media cleanup |
| Clock skew errors | NTP drift | chrony sync UTC |
Community: #synapse:matrix.org, GitHub matrix-org/synapse issues.
Migration из Slack
| Slack | Matrix |
|---|---|
| Workspace | Space |
| #channel | #room:matrix.example.com |
| @user | @user:matrix.example.com |
| Threads | Threads (native) |
| Integrations | Bots, webhooks, bridges |
Steps:
- Deploy Matrix stack (this guide)
- Create Space mirroring Slack structure
- mautrix-slack bridge for transition period (optional)
- Export Slack history (Compliance Export) → matrix-import tools
- Train team: Element desktop/mobile apps
- Run parallel 2–4 weeks
- Decommission Slack
Element apps: iOS, Android, Desktop (Electron) — all connect to your homeserver.
Migration из Telegram
- mautrix-telegram bridge for groups
- Personal: encourage DMs on Matrix, E2E by default
- Bot API bots → Matrix bots (maubot framework)
Telegram secret chats не bridge’ятся (E2E) — только public/supergroups.
Client apps recommendation
| Platform | App | Notes |
|---|---|---|
| Desktop | Element | Full featured |
| Desktop alt | Cinny, FluffyChat | Lightweight |
| iOS | Element X | Modern rewrite |
| Android | Element X | Push via UnifiedPush |
| CLI | weechat-matrix | Power users |
Configure custom homeserver URL on first launch: https://matrix.example.com.
Cost analysis vs SaaS
| Matrix self-hosted (4 GB VPS) | Slack Business+ | Telegram Premium | |
|---|---|---|---|
| 50 users/month | ~€15 VPS fixed | ~€600+ | N/A (not team) |
| Data control | Full | None | None |
| E2E | Optional rooms | No | Secret only |
| Setup time | 4–8 hours | 0 | 0 |
Окупается для команд 10+ на горизонте 2–3 месяцев.
Связка с экосистемой StormNet blog
- Identity — Authentik SSO
- Secrets — Vaultwarden, Vault
- Docs — BookStack wiki
- Monitoring — Uptime Kuma, Grafana
- VPN admin — Tailscale
- Email — Postfix/Dovecot
- Automation — n8n webhooks from Matrix bots
Production launch checklist
- DNS A + SRV + .well-known
- PostgreSQL + Synapse + coturn + Element running
- SSL valid all domains
- Federation tester green
- Admin user + cross-signing
- Registration disabled
- Backup cron configured + test restore
- Monitoring alerts
- Team onboarded on Element apps
- Moderation policy documented in BookStack
Итог
Matrix Synapse на VPS — единственный self-hosted мессенджер с open federation, E2E и bridges. Setup сложнее Slack, но вы владеете каждым событием, каждым файлом, каждой политикой retention.
VPS 4 GB+ — StormNet Cloud. Security — CrowdSec + Authentik. Backup — Restic + 3-2-1. Этот гайд покрывает полный цикл — от DNS до migration.
Рекомендуем прочитать
Частые вопросы
Matrix Synapse или Telegram для команды?
Telegram проще и бесплатен в облаке. Matrix — self-hosted, E2E, federation, compliance и полный контроль данных на VPS.
Сколько RAM нужно Synapse на VPS?
Минимум 2 GB для семьи, 4 GB для команды 20–100 человек. С SQLite в production не работайте — только PostgreSQL.
Нужен ли TURN сервер для Matrix?
Да, если нужны видеозвонки Element Call. coturn на том же VPS — стандартная практика.






