Synapse Homeserver 1.163.0
Overview
Synapse is the reference server for the Matrix protocol, an open standard for secure, decentralised real-time chat. It keeps your organisation's messages, rooms and files on an instance in your own AWS account. It can either federate with the wider Matrix network or run entirely private.
This guide covers launching and using the Synapse Homeserver 1.163.0 on Ubuntu 24.04 AMI from SecureAnyCloud on Amazon EC2. It is one version of the SecureAnyCloud team chat and video listing on AWS Marketplace. You pick it as the software version when you subscribe and launch.
What you get is a complete single-instance stack:
- Synapse 1.163.0 from the official
packages.matrix.orgrepository (Ubuntu's own archive carries a much older, unsupported release), run unmodified. - Element Web 1.12.32, the open-source Matrix web client, served from the same HTTPS address so people can sign in from a browser with nothing to install.
- coturn 4.18.0, a TURN and STUN relay, so voice and video calls connect even when both people sit behind strict firewalls or carrier-grade NAT.
- PostgreSQL 16 as the database and nginx as the only public web listener.
- A dedicated media volume: uploads, avatars and the remote media cache live on their own EBS volume at
/var/lib/matrix-synapse/media_store, so they can grow without touching the root volume.
What SecureAnyCloud adds on top:
- Every secret is generated on your instance at first boot. The homeserver signing key (its cryptographic identity),
registration_shared_secret,macaroon_secret_key,form_secret, the secret shared between Synapse and the TURN relay, the TLS certificate and a unique administrator password. Synapse reaches PostgreSQL over the local Unix socket with peer authentication, so there is no database password at all. Two instances launched from this AMI are distinct servers, and nothing secret is baked into the image. - Registration is closed. Nobody can create an account on your server unless you create it for them.
- Fail closed. Until first boot has finished, the web address answers HTTP 503 and Synapse and coturn refuse to start, so a half-configured homeserver is never reachable.
- HTTPS from the first boot. nginx serves the web client, the client API and
.well-knowndiscovery on 443 and federation on 8448 with a per-instance certificate. Port 80 redirects to HTTPS. - Fixed host names. nginx answers only for this server's configured name and addresses and closes the connection for anything else. Redirects always go to the configured name, never to whatever a request claims.
- Real client addresses. Synapse and nginx log the address the connection really came from. A client cannot spoof it with an
X-Forwarded-Forheader. - Instance metadata blocked. Every service (nginx, Synapse, coturn, PostgreSQL and the first-boot unit) is denied the EC2 instance metadata address. One small unit reads only this instance's own IP address at boot. Synapse refuses to fetch link-local, loopback and private addresses, and coturn refuses to relay to them. The AMI requires IMDSv2.
- Loopback-only internals. PostgreSQL and the Synapse process listen on
127.0.0.1only, and the Synapse admin API is not published through nginx. - Fully patched Ubuntu 24.04 LTS at build time, with unattended security upgrades switched on.
Default user: ubuntu; Architecture: x86_64; Recommended instance type: m5.large (2 vCPU, 8 GiB). Synapse, PostgreSQL and coturn together need more memory than a 4 GiB type gives them.
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. It ships the free and open-source software unmodified: Synapse under the GNU AGPL v3 or later, Element Web under the GNU AGPL v3 or GPL v3 (your choice), and coturn under the BSD 3-Clause licence. Sources: https://github.com/element-hq/synapse, https://github.com/element-hq/element-web and https://github.com/coturn/coturn.
Before you start: your server name is permanent
Every Matrix homeserver has a server name. It is part of every user ID (@alice:example.com), every room ID and every message signature the server produces, and it cannot be changed once people use the server.
- If you do nothing, first boot sets the server name to the instance's public IPv4 address. That works at once and suits evaluation, an internal deployment or a proof of concept. Use an Elastic IP if you keep it, because otherwise a stop and start changes the address.
- For anything permanent, and for federation, use your own domain, set before the first boot through user data (see "Using your own domain").
- If you have already booted and nobody has signed up,
matrix-set-server-namere-provisions the server under a new name. It is destructive by design and tells you whether it is still safe.
Prerequisites
- An AWS account subscribed to the listing, an EC2 key pair and a VPC subnet with a route to the internet.
- A security group that allows:
Port Protocol Purpose Open it to
22 TCP SSH administration your own networks only
80 TCP Redirects to HTTPS; ACME http-01 if you install a trusted certificate your users
443 TCP Element Web, the Matrix client API and .well-known discovery your users
8448 TCP Matrix federation; leave closed for a private server anywhere, only if you federate
3478 TCP and UDP TURN and STUN for call setup and relay your users
5349 TCP and UDP TURN over TLS and DTLS your users
49152-49231 UDP the bounded media relay range coturn allocates calls from your users
PostgreSQL (5432) and Synapse (8008) are never reachable from outside the instance, so do not open them.
Launch the instance
In the AWS Marketplace listing choose Continue to Subscribe, then Continue to Configuration, pick Synapse Homeserver 1.163.0 on Ubuntu 24.04 as the software version and your Region, then choose Launch through EC2.
Choose m5.large, your key pair and a security group as described above.
With the AWS CLI (replace the values in angle brackets; add --user-data file://server-name.yaml to set your own domain, see below):
aws ec2 run-instances --region <region> --image-id <ami-id-from-the-listing> \
--instance-type m5.large --key-name <your-key-pair> \
--security-group-ids <your-security-group> --subnet-id <your-subnet> \
--metadata-options HttpTokens=required,HttpEndpoint=enabled \
--tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=synapse}]'Connect to your instance
Version SSH user
Synapse Homeserver 1.163.0 on Ubuntu 24.04 ubuntu
ssh -i <your-key.pem> ubuntu@<public-ip>Check the services
First boot generates every secret, creates the database and the administrator, then opens the front door. It takes under a minute:
systemctl is-active postgresql@16-main matrix-synapse coturn nginx matrix-stack-firstboot
sudo test -f /var/lib/sac/data/matrix-stack-firstboot.done && echo FIRSTBOOT_DONEactive active active active active FIRSTBOOT_DONEIf you connect in the first minute and the web address answers HTTP 503, first boot is still running; follow it with sudo journalctl -u matrix-stack-firstboot -f.
The versions on the image, and the Matrix client API answering through nginx:
dpkg-query -W -f='${Package} ${Version}\n' matrix-synapse-py3 printf 'element-web %s\n' "$(cat /var/www/element/version)" printf 'coturn %s\n' "$(/usr/local/bin/turnserver --version)" curl -ks -o /dev/null -w 'client API -> HTTP %{http_code}\n' https://127.0.0.1/_matrix/client/versionsmatrix-synapse-py3 1.163.0+noble1 element-web v1.12.32 coturn 4.18.0 client API -> HTTP 200Get your credentials
sudo cat /root/matrix-stack-credentials.txtThe file is readable by root only:
matrix.server.name=<SERVER_NAME> matrix.web.url=https://<SERVER_NAME>/ matrix.homeserver.url=https://<SERVER_NAME> matrix.admin.user=<ADMIN_USER> matrix.admin.pass=<ADMIN_PASSWORD> matrix.admin.mxid=@<ADMIN_USER>:<SERVER_NAME> matrix.federation.port=8448 matrix.turn.realm=<SERVER_NAME>When you need to share it, for example in a support request, print it with the password masked:
sudo sed -E 's/^(matrix\.admin\.pass=).*/\1********/' /root/matrix-stack-credentials.txt | grep '^matrix\.'The signing key at /etc/matrix-synapse/homeserver.signing.key is this server's identity. It was generated on this instance and exists nowhere else: back it up with the database, and never copy it to another server.
Check the security model
Every socket bound to something other than loopback. This is the whole public surface, and it matches the security group table above (68 is the DHCP client):
sudo ss -lntuH | awk '{print $1, $5}' | grep -vE '127\.0\.0|\[::1\]|%lo' | sed -E 's/^(tcp|udp) (.*):([0-9]+)$/\1 \3/' | sort -u -k1,1 -k2,2ntcp 22 tcp 80 tcp 443 tcp 3478 tcp 5349 tcp 8448 udp 68 udp 3478 udp 5349PostgreSQL and the Synapse process listen on loopback only:
sudo ss -lntH | awk '{print $4}' | grep -E ':(5432|8008)$' | sort127.0.0.1:5432 127.0.0.1:8008Registration is closed, unknown host names are refused, and port 80 redirects to the configured name:
curl -ks -o /dev/null -w 'register -> HTTP %{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-123"}'
curl -ks -o /dev/null -w 'Host: evil.example -> %{http_code} (000 = connection closed)\n' -H 'Host: evil.example' https://127.0.0.1/ || true
S=$(sudo sed -n 's/^matrix\.server\.name=//p' /root/matrix-stack-credentials.txt)
curl -s -o /dev/null -w 'http:// -> %{http_code} %{redirect_url}\n' -H "Host: $S" http://127.0.0.1/register -> HTTP 403 Host: evil.example -> 000 (000 = connection closed) http:// -> 301 https://<SERVER_NAME>/Instance metadata is denied to every service; only the small address reader may reach it:
for u in nginx matrix-synapse coturn postgresql@16-main matrix-stack-firstboot; do printf '%-24s %s\n' "$u" "$(systemctl show -p IPAddressDeny --value $u.service)" donenginx 168.63.129.16/32 169.254.169.254/32 fd00:ec2::254/128 matrix-synapse 168.63.129.16/32 169.254.169.254/32 fd00:ec2::254/128 coturn 168.63.129.16/32 169.254.169.254/32 fd00:ec2::254/128 postgresql@16-main 168.63.129.16/32 169.254.169.254/32 fd00:ec2::254/128 matrix-stack-firstboot 168.63.129.16/32 169.254.169.254/32 fd00:ec2::254/128The administrator password authenticates and a wrong one is refused. The request bodies go to curl on standard input, so the password never appears in the process list or your shell history. Synapse rate limits sign ins, so a burst of attempts may answer 429 instead of 403; both mean refused:
sudo bash -c ' F=/root/matrix-stack-credentials.txt U=$(sed -n "s/^matrix\.admin\.user=//p" $F) body() { printf "{\"type\":\"m.login.password\",\"identifier\":{\"type\":\"m.id.user\",\"user\":\"%s\"},\"password\":\"%s\"}" "$U" "$1"; } login() { curl -ks -o /dev/null -w "%{http_code}" -X POST https://127.0.0.1/_matrix/client/v3/login -H "Content-Type: application/json" --data-binary @-; } GOOD=$(sed -n "s/^matrix\.admin\.pass=//p" $F | { read -r P; body "$P"; } | login) BAD=$(body "wrong-password-xyz" | login) case "$BAD" in 403|429) B="refused ($BAD)";; *) B="UNEXPECTED ($BAD)";; esac echo "correct password -> $GOOD; wrong password -> $B" [ "$GOOD" = 200 ] && case "$BAD" in 403|429) true;; *) false;; esac 'correct password -> 200; wrong password -> refused (403)Sign in to Element
Open https://<SERVER_NAME>/ in a browser. Use the public IP unless you set a domain.
The certificate is self-signed for this instance, so the browser warns you on the first visit. Accept it, or install a trusted certificate as described below. Sign in with the administrator user and password from Step 4. You land on your own homeserver.
Matrix verifies devices against each other so end-to-end encrypted history can be shared safely. The first device to sign in sets up the account's encryption identity; later devices are asked to confirm against one you already trust. That is normal Matrix behaviour.
Create a room and send a message
Use the + beside Home to create a room, invite people and type into the composer. Rooms you create from Element are end-to-end encrypted by default. The room below was created through the API without encryption, which Element points out at the top of the timeline.
Settings, Account shows the full Matrix ID your homeserver issued, in the form @user:server-name.
Voice and video calls
Calls between people on restrictive networks are relayed by coturn. Synapse gives each signed-in client a short-lived TURN credential derived from a secret it shares with the relay, so there is no static call password to leak:
sudo bash -c ' F=/root/matrix-stack-credentials.txt U=$(sed -n "s/^matrix\.admin\.user=//p" $F) for i in $(seq 1 12); do T=$(sed -n "s/^matrix\.admin\.pass=//p" $F | python3 -c "import json,sys; print(json.dumps({\"type\":\"m.login.password\",\"identifier\":{\"type\":\"m.id.user\",\"user\":sys.argv[1]},\"password\":sys.stdin.read().strip()}))" "$U" \
| curl -ks -X POST https://127.0.0.1/_matrix/client/v3/login -H "Content-Type: application/json" --data-binary @- \
| python3 -c "import json,sys; print(json.load(sys.stdin).get(\"access_token\",\"\"))") [ -n "$T" ] && break; sleep 10 done [ -n "$T" ] || { echo "sign in is rate limited; wait a few minutes and retry"; exit 1; } H=$(mktemp /run/turn.XXXXXX); printf "Authorization: Bearer %s\n" "$T" > $H curl -ks -H @$H 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\"]]" curl -ks -H @$H -X POST -d "{}" https://127.0.0.1/_matrix/client/v3/logout >/dev/null; rm -f $H 'ttl: 86400 uri: turn:<SERVER_NAME>:3478?transport=udp uri: turn:<SERVER_NAME>:3478?transport=tcp uri: turns:<SERVER_NAME>:5349?transport=tcpThe relay refuses to forward traffic to loopback, private ranges, link-local addresses and the EC2 instance metadata address 169.254.169.254, so an authenticated user cannot use it to reach your VPC:
sudo grep -cE '^denied-peer-ip=' /etc/coturn/turnserver.conf
sudo grep -E '^denied-peer-ip=169\.254|^no-multicast-peers|^use-auth-secret' /etc/coturn/turnserver.conf20 use-auth-secret no-multicast-peers denied-peer-ip=169.254.0.0-169.254.255.255Add users
Registration is closed, so you create accounts. register_new_matrix_user reads the shared secret from this instance's own configuration. Give it the new password through a root-only file rather than on the command line, so it is never written to /var/log/auth.log or your shell history:
sudo install -m 600 /dev/null /root/newuser.pass
sudo nano /root/newuser.pass
# type the new user's password, save sudo /opt/venvs/matrix-synapse/bin/register_new_matrix_user \
-c /etc/matrix-synapse/conf.d/90-sac-instance.yaml \
-u alice --password-file /root/newuser.pass --no-admin http://127.0.0.1:8008 sudo shred -u /root/newuser.passUse --admin instead of --no-admin for another server administrator. Run it without --password-file and it prompts for the password instead.
To let people sign up themselves, set enable_registration: true in /etc/matrix-synapse/conf.d/10-sac.yaml, pair it with email verification or a registration token so you are not an open relay for spam, and sudo systemctl restart matrix-synapse. Synapse also supports SAML, CAS and OpenID Connect single sign on; configure your provider in the same directory.
The Synapse admin API listens on loopback only. Reach it over an SSH tunnel, for example ssh -L 8008:127.0.0.1:8008 ubuntu@<public-ip>, then call http://127.0.0.1:8008/_synapse/admin/... with an administrator's access token.
Using your own domain
Check whether it is still safe to change the server name:
sudo matrix-set-server-name --checkOn a freshly launched instance this reports only the first boot administrator and no rooms. Once people have signed up it says the change is no longer safe.
The best way is to never boot with the wrong name. Pass user data at launch so the name is in place before first boot:
#cloud-config write_files: - path: /etc/sac/matrix-stack/server-name permissions: '0644' content: | matrix.your-domain.exampleIf the instance is already running and nobody has signed up, re-provision it. This destroys the database, the signing key and every account and creates a new administrator, because a server name cannot be renamed in place:
sudo matrix-set-server-name matrix.your-domain.example --yes sudo cat /root/matrix-stack-credentials.txtPoint a DNS A record for the name at the instance's Elastic IP, then install a trusted certificate:
sudo apt-get install -y certbot python3-certbot-nginx
sudo certbot --nginx -d matrix.your-domain.examplePoint coturn at the same certificate by editing cert= and pkey= in /etc/coturn/turnserver.conf, then sudo systemctl restart coturn.
For short user IDs (@alice:your-domain.example) while the server runs at matrix.your-domain.example, set the server name to the short domain before first boot and publish the two .well-known/matrix documents this instance serves from the web server at that short domain.
Federation
Federation lets your users talk to people on other Matrix servers. It needs port 8448 open, a real domain and a certificate other servers trust. What this server advertises:
curl -ks https://127.0.0.1/.well-known/matrix/server; echo
curl -ks -o /dev/null -w 'federation API -> HTTP %{http_code}\n' https://127.0.0.1:8448/_matrix/federation/v1/version{ "m.server": "<SERVER_NAME>:8448" } federation API -> HTTP 200For a private deployment, leave 8448 closed in the security group. Everything else keeps working; your users simply cannot reach or be reached by other homeservers. Synapse never fetches link-local, loopback or private addresses when talking to other servers or downloading remote media (ip_range_blacklist), and URL previews are off by default.
Media storage
Uploads, avatars and cached remote media live on their own EBS volume, so you can grow it without touching the root volume:
df -h --output=source,size,target /var/lib/matrix-synapse/media_store | tail -1To grow it, modify the volume in the EC2 console, then sudo resize2fs the device shown above.
Backups
Back up three things together: the PostgreSQL database, the signing key and the media store. The signing key cannot be regenerated; 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_storeEBS snapshots of both volumes, taken together, are an equally good whole server backup.
Updates
Ubuntu security updates install automatically. Synapse updates come from the official repository:
sudo apt-get update && sudo apt-get install --only-upgrade matrix-synapse-py3Support
SecureAnyCloud supports this image. Contact sales@secureanycloud.com with your AWS account ID, the Region and the instance ID. For choosing between the versions in this listing, see the team chat and video guide.
Deploy on AWS
Launch Synapse Homeserver 1.163.0 on Ubuntu 24.04 on AWS with 24/7 support from SecureAnyCloud.
View on Marketplace
Need Help?
Our support team is available 24/7.
sales@secureanycloud.com