VPS Tutorials
Deploy Matrix Synapse on a VPS (2026): Docker, Federation, Element, HTTPS and Backups
Deploy Matrix Synapse from scratch on an Ubuntu VPS: use the official Docker image and PostgreSQL, configure Element, HTTPS, Matrix federation, well-known, SMTP, TURN, backups and upgrades.

Matrix Synapse is a homeserver implementation of the Matrix protocol, well suited to keeping team chat, private communities and cross-server federated communication in your own hands. It differs from an ordinary instant-messaging web app: besides the login page, you must correctly handle the Server Name, event signing, federation discovery, media storage, email and backups.
This article uses an Ubuntu 24.04 LTS VPS as an example, deploying Synapse with the official matrixdotorg/synapse Docker image and providing HTTPS through a Caddy reverse proxy. The examples cover PostgreSQL, the Element client, federation ports, /.well-known, admin accounts, TURN, upgrades and recovery. The official Synapse documentation states that the Server Name cannot be changed after installation, so domain planning must be completed in the first step.
A Matrix user ID has the format @user:server.name, where server.name is the homeserver's Server Name. It is not a simple web domain alias; once written into the database and event signatures, it cannot be migrated or changed.
There are two common planning approaches:
| Approach | Example | Suitable for |
|---|---|---|
| Main domain as Server Name | example.com | Users should use @alice:example.com |
| Subdomain as Server Name | matrix.example.com | Does not affect the main site, simpler to deploy |
If you choose example.com, you can put the client access address at matrix.example.com and then use /.well-known/matrix/client to tell Element the actual homeserver address. To reduce complexity, it is safer to use matrix.example.com as both the Server Name and the access domain.
| Usage scale | Recommended configuration | Notes |
|---|---|---|
| Personal testing | 2 cores, 2 GB, 30 GB SSD | SQLite can be used to validate the flow, not suitable for public operation |
| Small team | 2-4 cores, 4 GB, 60 GB NVMe | Plan PostgreSQL, media and backups separately |
| Public community | 4+ cores, 8 GB+, 100 GB+ NVMe | Reserve capacity for media, search, thumbnails and federation traffic |
Synapse's media files often grow faster than the database. Do not buy disk based only on container image size; also account for uploaded images, thumbnails, remote federated media, PostgreSQL WAL, logs and off-site backups. You can first refer to the VPS configuration selection guide, then estimate capacity based on user count and media retention period.
Prepare a fresh 64-bit Ubuntu VPS, root or sudo privileges, a fixed public IP, and a domain that has already been planned. First set up DNS:
matrix.example.com A 203.0.113.10
element.example.com A 203.0.113.10
~~
如果 Server Name 使用主域名,还需要保留主域名的 `/.well-known` 路径。检查解析、端口和资源:
~~~bash
dig +short matrix.example.com A
curl -4 ifconfig.me
sudo ss -lntup
free -h
df -h
云防火墙和 UFW 可以先开放:
| 端口 | 用途 | 建议 |
|---|---|---|
| 22/TCP | SSH 管理 | 只允许管理 IP 更安全 |
| 80/TCP | ACME HTTP-01 和跳转 | 公网开放 |
| 443/TCP | 客户端 HTTPS、联邦入口 | 公网开放 |
| 8448/TCP | 传统 Matrix 联邦端口 | 使用 443 代理时可按架构决定 |
| 3478/TCP、3478/UDP | coturn TURN | 需要语音视频时开放 |
| 49152-65535/UDP | TURN relay | 按 coturn 端口范围开放 |
PostgreSQL、Redis(如另行部署)和 Synapse 管理接口不要暴露公网。若 VPS 上已有 Nginx、Apache、Caddy 或面板,先确认 80/443 没有冲突。
官方镜像目前使用 matrixdotorg/synapse:latest 标签,生产环境应在验证后固定到明确版本。本文写作时官方最新 Release 为 v1.159.0,上线前请查看 Synapse Releases 再决定是否升级。
创建持久化目录并生成配置:
sudo mkdir -p /opt/synapse/data
sudo chown -R 991:991 /opt/synapse/data
sudo docker run --rm \
--mount type=bind,src=/opt/synapse/data,dst=/data \
-e SYNAPSE_SERVER_NAME=matrix.example.com \
-e SYNAPSE_REPORT_STATS=no \
matrixdotorg/synapse:v1.159.0 generate
官方 Docker 文档说明,generate 会在 /data 中生成 homeserver.yaml、签名密钥和日志配置。Server Name 只在生成时确定;如果要使用 example.com 作为用户域名,应将环境变量改成 SYNAPSE_SERVER_NAME=example.com,不要先用子域名测试后再改回主域名。
检查生成的配置:
sudo grep -E '^(server_name|public_baseurl|listeners|database):' /opt/synapse/data/homeserver.yaml
sudo chmod 750 /opt/synapse/data
SQLite 适合个人试用和迁移演练,但公开服务建议使用 PostgreSQL。可以在同一台 VPS 使用 Docker Compose 管理数据库和 Synapse,数据库只加入内部网络:
services:
postgres:
image: postgres:16
restart: unless-stopped
environment:
POSTGRES_USER: synapse
POSTGRES_PASSWORD: change-this-long-password
POSTGRES_DB: synapse
volumes:
- ./postgres:/var/lib/postgresql/data
networks: [synapse-internal]
synapse:
image: matrixdotorg/synapse:v1.159.0
restart: unless-stopped
depends_on: [postgres]
volumes:
- ./data:/data
ports:
- "127.0.0.1:8008:8008"
networks: [synapse-internal]
networks:
synapse-internal:
driver: bridge
在 homeserver.yaml 中配置 PostgreSQL 的数据库名、用户、密码和服务名 postgres。不要把密码提交到 Git,也不要把 5432 映射到公网。切换数据库前先做完整备份,并按照官方 PostgreSQL 配置文档检查字符集和迁移要求。
完成数据库配置后启动:
cd /opt/synapse
sudo docker compose up -d
sudo docker compose ps
sudo docker compose logs --tail=200 synapse
curl -fsS http://127.0.0.1:8008/_matrix/federation/v1/version
8008 只绑定到回环地址,公网请求由反向代理转发。官方镜像不再根据环境变量自动生成配置,必须先存在有效的 homeserver.yaml,否则容器会直接退出。遇到 permission denied 时,检查宿主机目录是否允许容器 UID 991 读取配置并写入 media、logs 等目录。
Synapse 的客户端 API、媒体下载和联邦请求都应该使用有效 HTTPS。Caddy 示例:
matrix.example.com {
reverse_proxy 127.0.0.1:8008
}
启动 Caddy 后检查:
curl -I https://matrix.example.com/_matrix/client/versions
curl -fsS https://matrix.example.com/_matrix/federation/v1/version
在 homeserver.yaml 中确认 public_baseurl 与真实 HTTPS 地址一致,例如 https://matrix.example.com/。不要在 Cloudflare Flexible 模式下代理,使用 Full (strict) 并确保源站证书有效。若已经有站点使用 Caddy,可按域名拆分站点块,但不要让另一个代理吞掉 Matrix 的长连接和请求头。
联邦让不同 homeserver 之间交换房间事件。最容易出错的是 Server Name、证书和端口发现。
如果 Server Name 是 matrix.example.com,可以让该域名的 8448/TCP 直接进入 Synapse 的 TLS 监听器。云防火墙、Caddy 或 Nginx 必须允许 8448,证书中的域名也要匹配。
更常见的做法是用 443 接收 HTTPS,然后在 Server Name 所在域名发布:
{
"m.server": "matrix.example.com:443"
}
如果 Server Name 与访问域名不同,例如 Server Name 是 example.com,需要在主站提供:
https://example.com/.well-known/matrix/server
~~
The response headers must include at least `Content-Type: application/json`, and the response body is the JSON above. The Element client can also use:
~~~json
{
"m.homeserver": {
"base_url": "https://matrix.example.com"
}
}
The corresponding path is /.well-known/matrix/client. After deployment, use curl -i to check the status code, Content-Type, JSON format and certificate chain. For federation testing you can use the Matrix Federation Tester; do not judge that federation is working only because the local API returns 200.
To create the first admin, you need to temporarily set registration_shared_secret in homeserver.yaml, then restart the container:
sudo docker compose restart synapse
sudo docker compose exec synapse register_new_matrix_user \
http://localhost:8008 \
-c /data/homeserver.yaml \
--admin
After creation, delete registration_shared_secret and restart again. This key is only used for the registration script; it should not be kept long-term, and it should not be copied into tickets or public logs.
Element Web can be deployed separately at element.example.com, or you can first use the official hosted client to log in to your custom homeserver. When self-hosting Element Web, configure config.json:
{
"default_server_config": {
"m.homeserver": {
"base_url": "https://matrix.example.com",
"server_name": "matrix.example.com"
}
}
}
First test login, room creation, media upload, inviting users from external homeservers and device logout with two ordinary accounts, then open registration. Before public registration, you should also configure captcha, rate limiting and email verification to avoid bulk bot registrations.
Email is used for password resets, registration verification and notifications. Use the transactional email provider's 587/TLS port, and configure SPF, DKIM and DMARC in DNS. Store the email password in a restricted configuration file or secrets manager; do not write real credentials anywhere other than the article examples.
The directory containing media_store_path will keep growing. You can set a media retention policy in the Synapse configuration and monitor the directory size with disk alerts. Do not directly delete files under media_store, otherwise invalid references may remain in the database.
The official Synapse Docker image does not include a TURN service. If you need voice/video or compatibility with restricted networks, deploy coturn separately, set up static authentication or short-term credentials, and configure it according to the official documentation in configuration items such as turn_uris and turn_shared_secret. After opening the TURN relay ports, restrict abuse; static usernames and passwords are exposed to clients, so prefer short-term credentials in production.
Back up at least the following:
/opt/synapse/data/homeserver.yaml
/opt/synapse/data/*.signing.key
/opt/synapse/data/media_store/
/opt/synapse/postgres/
/opt/synapse/compose.yaml
For the database, use native PostgreSQL backups or consistent snapshots; configuration and signing keys must be encrypted and stored off-site. You can combine this with How to do a VPS backup and recovery drill to practice in practice: create a temporary VPS, restore the database and /data, then verify the result by logging in with Element and running a federation test.
Before upgrading, record the version, create a VPS snapshot and a database backup:
sudo docker compose exec synapse python -m synapse.app.homeserver --version
sudo docker compose pull synapse
sudo docker compose up -d synapse
sudo docker compose logs --tail=200 synapse
Do not use latest long-term in production. Read the Release Notes first, validate the database migration on a staging or temporary VPS, then update the image tag to the target version. After upgrading, check login, media, email, federation and background tasks, rather than only checking that the container status is Up.
sudo docker compose logs synapse
sudo ls -l /opt/synapse/data/homeserver.yaml
sudo docker compose config
Focus on checking whether homeserver.yaml exists, YAML indentation, the database connection, container UID permissions and whether the signing key is readable.
Check the DNS for the Server Name, port 8448/TCP or 443, /.well-known/matrix/server, the certificate SAN and reverse proxy request headers. If using Cloudflare, confirm that the proxy mode does not block federation requests, and run the Federation Tester from an external network.
curl -i https://matrix.example.com/_matrix/client/versions
curl -i https://example.com/.well-known/matrix/client
Confirm public_baseurl, CORS, JSON Content-Type and the certificate chain; do not mistake the domain used to access Element Web in the browser for the homeserver address.
df -h
sudo du -sh /opt/synapse/data/media_store
sudo docker system df
First clean up unused Docker images and logs, then handle media according to the retention policy. Do not manually delete media files without database records and backups.
Check whether coturn is reachable, whether the relay port range is open, whether the VPS public IP is correct, and whether short-term credentials have expired. TURN only solves NAT traversal; it does not replace sufficient outbound bandwidth.
- Server Name is decided and there are no plans to change it
- Synapse uses a fixed version tag and does not directly depend on
latest - PostgreSQL is configured and 5432 is not exposed to the public internet
- 8008 only listens on loopback or the internal network
- 443 HTTPS, certificate and
public_baseurlare consistent - 8448 or
/.well-known/matrix/serverfederation discovery has passed external testing - Element login, room creation, invitations, media upload and device logout have all been tested
- SMTP, SPF, DKIM, DMARC and password reset emails work
- Admin account has 2FA enabled, and registration policy and rate limits are set
- coturn is deployed only when needed, with relay ports and credential abuse restricted
- Configuration, signing keys, database and media have off-site backups
- A recovery and version upgrade drill has been completed in a temporary environment
The hard part of Matrix Synapse is not getting a container to start, but making the identity domain, federation discovery, database, media, email and backups form a complete loop. First validate with a 2-core 4GB VPS and a separate subdomain, then scale up based on media growth and federation traffic; this is usually easier to maintain than piling on complex components from the start.