Synapse homeserver
Overview
Synapse is the reference server implementation of the Matrix protocol, an open standard for secure, decentralised real-time communication. It holds your organisation's messages, rooms and uploaded media on infrastructure inside your own Azure subscription. It can either federate with the wider public Matrix network or run entirely private.
This guide covers the deployment and configuration of a Synapse homeserver on Ubuntu 24.04 on Azure, using SecureAnyCloud Azure Marketplace images. The image is a complete single-node stack, not just the server process. Alongside Synapse it ships a browser-based Matrix web client, so users can sign in and start talking without installing anything. It also ships coturn, a TURN and STUN relay, so voice and video calls still connect when both participants sit behind restrictive firewalls or carrier-grade NAT.
Nothing in this image ships with a known secret. Every VM generates its own secrets on first boot, so two VMs created from this image are cryptographically distinct servers. Open registration is closed by default, so nobody can create an account on your server without an invitation.
Matrix and the Matrix logo are trademarks of The Matrix.org Foundation C.I.C. Element is a registered trademark of Element Creations Ltd. coturn is an independent open-source project. This image is produced by SecureAnyCloud and is not affiliated with, sponsored by, or endorsed by The Matrix.org Foundation, Element Creations Ltd, or the coturn project.
The image ships the free and open-source software unmodified: Synapse and the web client under the GNU AGPL v3 (the web client is additionally offered under the GNU GPL v3), and coturn under the BSD 3-Clause licence. Corresponding sources are at https://github.com/element-hq/synapse, https://github.com/element-hq/element-web and https://github.com/coturn/coturn.
What you get:
- Synapse 1.160.0, installed from the official
packages.matrix.orgrepository instead of the much older package in Ubuntu's own archive, and run unmodified under systemd - Element Web 1.12.27, the open-source Matrix web client, served as static files from the same TLS listener and shipped unmodified
- coturn 4.18.0, built from the upstream release because Ubuntu 24.04 still carries 4.6.1, and hardened so the relay cannot be used to reach your private network
- PostgreSQL 16 as the homeserver database, bound to loopback with no host port published
- nginx as the single public listener, terminating TLS for the web client, the client API and federation
- A dedicated media volume. Uploads, avatars and the remote media cache live on their own resizable Azure data disk, not on the OS disk
- First-boot secret generation. The signing key (Synapse's cryptographic identity), database password,
registration_shared_secret,macaroon_secret_key,form_secret, the shared secret between the homeserver and the TURN relay, TLS certificate and a unique administrator password are all unique to each VM - 24/7 SecureAnyCloud support
Before you start: your server name is permanent
This is the one decision on this page that you cannot undo later, so it comes first.
Every Matrix homeserver has a server name. It is more than an address. It is baked into every user ID (@alice:example.com), every room ID and every event signature your server ever produces. You cannot change it once people have started using the server. Changing it means starting a new homeserver.
The image handles this in the safest way by default, and it still lets you do the right thing:
- If you do nothing, first boot sets the server name to the VM's public IP address. The appliance works immediately and suits evaluation, an internal deployment reached by IP, or a proof of concept.
- If you want a real domain, set it before the first boot. You almost certainly want one for anything permanent, and you need one for federation. Pass cloud-init custom data that writes the name into
/etc/sac/matrix-stack/server-name. See "Using your own domain" below. - If you have already booted and nobody has signed up yet,
matrix-set-server-namere-provisions the server under a new name. It is destructive by design, and it tells you whether it is still safe to run.
Prerequisites
- An Azure subscription with permission to create virtual machines
- An SSH key pair for VM access
- A VM size of Standard_B2ms or larger (2 vCPU, 8 GiB RAM). Synapse, PostgreSQL and coturn together need more memory than a 4 GiB size can give them without swapping, and the image deliberately ships no swap
- Inbound network security group rules for the ports listed under "Step 6"
- Optionally, a DNS name you control, if you want a real domain and federation
Deploy from the Azure Portal
- In the Azure Portal, search the Marketplace for Synapse Homeserver on Ubuntu 24.04 LTS and select the SecureAnyCloud offer.
- Choose Standard_B2ms or larger.
- Set the administrator username to
azureuserand upload your SSH public key. - On the Networking tab, allow inbound SSH (22) and HTTPS (443) to begin with. Add the call and federation ports from Step 6 before you invite users.
- Select Review + create, then Create.
Deploy from the Azure CLI
az vm image terms accept --urn sac1647283583153:matrix-synapse-element-coturn-ubuntu-24-04:default:latest
az vm create \
--resource-group my-matrix-rg \
--name my-synapse-vm \
--image sac1647283583153:matrix-synapse-element-coturn-ubuntu-24-04:default:latest \
--size Standard_B2ms \
--admin-username azureuser \
--generate-ssh-keys \
--public-ip-sku StandardConnect to your VM
ssh azureuser@<vm-ip>Confirm the services are running
The stack is four services. matrix-stack-firstboot is a one-shot unit that runs once, on the very first boot. It mints everything this VM needs before the homeserver is allowed to start.
systemctl is-active postgresql matrix-synapse coturn nginxExpected output:
active active active activeIf you connect within the first minute or so of a cold boot, the web address may answer HTTP 503. That is deliberate. nginx refuses to serve anything until first boot has finished writing this VM's own secrets, so there is no window in which a half-configured homeserver is reachable. Watch first boot complete with:
sudo systemctl status matrix-stack-firstboot.service --no-pagerConfirm the Matrix client API is answering through nginx:
curl -ks -o /dev/null -w '%{http_code}\n' https://127.0.0.1/_matrix/client/versionsExpected output:
200Retrieve your per-instance credentials
Every VM generates its own administrator on first boot. The credentials are written to a file that only root can read:
sudo cat /root/matrix-stack-credentials.txtThis file contains:
matrix.server.name: this homeserver's permanent server namematrix.web.url: the address to open in a browsermatrix.admin.user/matrix.admin.pass/matrix.admin.mxid: the per-instance administratormatrix.federation.port: the port other homeservers use to reach yoursmatrix.turn.realm: the realm the TURN relay authenticates against
The homeserver's signing key, at /etc/matrix-synapse/homeserver.signing.key, is this server's cryptographic identity. It is generated on this VM and exists nowhere else. Back it up with your database, and never copy it to another server.
Understand the security model and open the right ports
Do not just check that the ports you expect are open. List every socket bound to something other than loopback, so anything unexpected has nowhere to hide:
sudo ss -lntuH | awk '{print $1, $5}' | grep -vE '127\.0\.0|\[::1\]' | sort -uThat is the complete public surface, and it is what your network security group needs to allow:
- 22 (TCP): SSH administration
- 80 (TCP): redirects to HTTPS, and serves ACME http-01 if you install a trusted certificate
- 443 (TCP): the web client, the Matrix client API and .well-known discovery
- 8448 (TCP): Matrix federation. Leave this closed if you want a private, non-federating server
- 3478 (TCP and UDP): TURN and STUN, for call setup and relay
- 5349 (TCP and UDP): the same over TLS and DTLS, for networks that only permit HTTPS-like traffic
- 49152-49231 (UDP): the bounded media relay range that coturn allocates calls from, one port per live call leg
Everything else stays on loopback. PostgreSQL and the Synapse process itself are never reachable from off the VM:
sudo ss -lntH | grep -E ':(5432|8008)' | awk '{print $1, $4}'Expected output:
LISTEN 127.0.0.1:5432 LISTEN 127.0.0.1:8008Open registration is closed, so a stranger who finds your server cannot create an account on it:
curl -ks -o /dev/null -w '%{http_code}\n' -X POST https://127.0.0.1/_matrix/client/v3/register \
-H 'Content-Type: application/json' -d '{"username":"walkup","password":"not-allowed"}'Expected output:
403Confirm that the per-instance administrator password authenticates, and that a wrong one does not:
ADMIN_USER=$(sudo grep '^matrix.admin.user=' /root/matrix-stack-credentials.txt | cut -d= -f2-)
ADMIN_PASS=$(sudo grep '^matrix.admin.pass=' /root/matrix-stack-credentials.txt | cut -d= -f2-)
GOOD=$(curl -ks -o /dev/null -w '%{http_code}' -X POST https://127.0.0.1/_matrix/client/v3/login \
-H 'Content-Type: application/json' \
-d "{\"type\":\"m.login.password\",\"identifier\":{\"type\":\"m.id.user\",\"user\":\"${ADMIN_USER}\"},\"password\":\"${ADMIN_PASS}\"}")
BAD=$(curl -ks -o /dev/null -w '%{http_code}' -X POST https://127.0.0.1/_matrix/client/v3/login \
-H 'Content-Type: application/json' \
-d "{\"type\":\"m.login.password\",\"identifier\":{\"type\":\"m.id.user\",\"user\":\"${ADMIN_USER}\"},\"password\":\"wrong-password-xyz\"}") case "${BAD}" in 403|429) VERDICT="rejected (${BAD})" ;; *) VERDICT="UNEXPECTED (${BAD})" ;; esac echo "correct password -> ${GOOD}; wrong password -> ${VERDICT}"Expected output:
correct password -> 200; wrong password -> rejected (403)Synapse rate-limits sign-in attempts, so a run of failed logins starts returning 429 instead of 403. Both mean that Synapse refused the credential. 429 means that Synapse stopped answering in detail because it was being hammered. That protection is on by default, and it is why the check above accepts either code.
Sign in to the web client
Open https://<vm-ip>/ in a browser. The image ships a self-signed certificate generated for this VM, so your browser warns on the first visit. Accept the warning, or install a trusted certificate as described below.
Sign in with the administrator username and password from Step 5. You land on an empty account on your own homeserver.
Signing in on an additional device. Matrix verifies devices against each other so that end-to-end encrypted history can be shared safely. The first device to sign in to an account sets up that account's encryption identity by itself. Every later device must confirm itself against one you already trust.
If you no longer have a trusted device, the client offers a "Can't confirm?" path that resets the identity and starts again. That is normal Matrix behaviour, not a fault with the image.
Create a room and send a message
Use the + button beside the room list to create a room, then invite people to it and type into the composer at the bottom. Private rooms are end-to-end encrypted by default, so the server stores only ciphertext for them.
Your account settings show the full Matrix ID your homeserver issued you, in the form @user:server-name.
Confirm voice and video calling is wired up
coturn relays calls between people on restrictive networks. Synapse hands each signed-in client a short-lived TURN credential, derived from a secret it shares with the relay, so there is no static call login to leak:
ADMIN_USER=$(sudo grep '^matrix.admin.user=' /root/matrix-stack-credentials.txt | cut -d= -f2-)
ADMIN_PASS=$(sudo grep '^matrix.admin.pass=' /root/matrix-stack-credentials.txt | cut -d= -f2-)
# Synapse rate limits sign ins, so back off and retry rather than failing outright if
# you have just run the checks above. for attempt in $(seq 1 12); do
TOKEN=$(curl -ks -X POST https://127.0.0.1/_matrix/client/v3/login \
-H 'Content-Type: application/json' \
-d "{\"type\":\"m.login.password\",\"identifier\":{\"type\":\"m.id.user\",\"user\":\"${ADMIN_USER}\"},\"password\":\"${ADMIN_PASS}\"}" \
| python3 -c 'import json,sys; print(json.load(sys.stdin).get("access_token",""))') [ -n "${TOKEN}" ] && break sleep 10 done [ -n "${TOKEN}" ] || { echo "could not sign in to read TURN settings — Synapse is still rate limiting; wait a few minutes"; exit 1; } curl -ks -H "Authorization: Bearer ${TOKEN}" https://127.0.0.1/_matrix/client/v3/voip/turnServer \
| python3 -c 'import json,sys; d=json.load(sys.stdin); print("ttl:", d["ttl"]); [print("uri:", u) for u in d["uris"]]'The expected output follows. The URIs carry your own server name:
ttl: 86400 uri: turn:<SERVER_NAME>:3478?transport=udp uri: turn:<SERVER_NAME>:3478?transport=tcp uri: turns:<SERVER_NAME>:5349?transport=tcpThe relay is deliberately restricted. It refuses to forward traffic to loopback, to private ranges, or to the Azure instance metadata address at 169.254.169.254. Without that restriction, an authenticated user could point the relay at your own network and read instance metadata through it.
Add users
Registration is closed, so you create accounts explicitly. The following command uses the registration_shared_secret that first boot generated for this VM:
sudo /opt/venvs/matrix-synapse/bin/register_new_matrix_user \
-c /etc/matrix-synapse/conf.d/90-sac-instance.yaml \
-u alice -p 'choose-a-strong-password' --no-admin http://127.0.0.1:8008 \
|| echo "That username is already registered on this homeserver — choose another."Add --admin instead of --no-admin to create another server administrator.
To let people sign up for themselves instead, set enable_registration: true in /etc/matrix-synapse/conf.d/10-sac.yaml. Pair it with a verification method or a registration token so that you are not running an open relay for spam. Then restart with sudo systemctl restart matrix-synapse.
Synapse also supports SAML, CAS and OpenID Connect single sign-on. Configure your provider in the same directory.
Using your own domain
Check whether it is still safe to change the server name:
sudo matrix-set-server-name --checkOn a freshly deployed VM, this reports that only the first-boot administrator exists and that a change is still safe.
The best approach is never to boot with the wrong name. Pass cloud-init custom data at deployment time, so the name is in place before first boot runs:
#cloud-config write_files: - path: /etc/sac/matrix-stack/server-name permissions: '0644' content: | matrix.your-domain.exampleIf the VM is already running and nobody has signed up yet, re-provision it. A Matrix server name cannot be renamed in place, so this destroys the database, the signing key and every account and creates a new administrator:
sudo matrix-set-server-name matrix.your-domain.example --yes sudo cat /root/matrix-stack-credentials.txtPoint an A record for that name at the VM's public IP. Then install a trusted certificate, so that browsers and other homeservers accept it:
sudo apt-get install -y certbot python3-certbot-nginx
sudo certbot --nginx -d matrix.your-domain.exampleCertbot writes the certificate into the nginx configuration. Point coturn at the same files by editing cert= and pkey= in /etc/coturn/turnserver.conf, then sudo systemctl restart coturn.
Serving the homeserver from a shorter user ID
If you want IDs like @alice:your-domain.example while the server itself runs at matrix.your-domain.example, use the .well-known delegation that this image already serves. The image publishes /.well-known/matrix/server and /.well-known/matrix/client on the VM.
To use the shorter form, set the server name to the short domain before first boot, and serve those two documents from the web server at that short domain. Doing it afterwards is the destructive path above.
Federation
Federation lets your users talk to people on other Matrix servers. It needs port 8448 open, a real domain, and a certificate that other servers trust. Check what your server advertises:
curl -ks https://127.0.0.1/.well-known/matrix/serverFor a private, internal-only deployment, leave 8448 closed in your network security group. Everything else keeps working, but your users cannot reach, and cannot be reached by, other homeservers.
Media storage
Uploads, avatars and cached remote media live on a dedicated Azure data disk mounted at /var/lib/matrix-synapse/media_store, so they can grow without touching the OS disk and can be resized independently:
df -h /var/lib/matrix-synapse/media_storeBackups
Back up three things together: the PostgreSQL database, the signing key, and the media store. You cannot regenerate the signing key. Without it, a restored server is a different server.
sudo -u postgres pg_dump synapse | gzip > /var/backups/synapse-$(date +%F).sql.gz
sudo cp /etc/matrix-synapse/homeserver.signing.key /var/backups/
sudo tar czf /var/backups/media-$(date +%F).tar.gz -C /var/lib/matrix-synapse media_storeSupport
SecureAnyCloud publishes and supports this image, with 24/7 support. Contact sales@secureanycloud.com with your Azure subscription ID and the VM name.
unattended-upgrades applies operating system security updates automatically. To update Synapse itself from the official repository, run sudo apt-get update && sudo apt-get install --only-upgrade matrix-synapse-py3.
Deploy on Azure
Launch Synapse Homeserver on Ubuntu 24.04 LTS on Azure with 24/7 support from SecureAnyCloud.
View this image
Need Help?
Our support team is available 24/7.
sales@secureanycloud.com