A self-hosted Matrix homeserver stores your users' accounts and room history and exchanges signed events with other Matrix servers over HTTPS. Messages can be end-to-end encrypted, but the server still sees who talks to whom and when. Run Synapse on PostgreSQL behind a TLS proxy, keep registration closed, back up the signing key and patch quickly.
This guide goes one level deeper than our explainer on end-to-end encrypted messaging, the Double Ratchet, MLS and metadata and our general post on whether it is safe to self-host apps on the internet. If you are building a wider self-hosted stack, the guides to replacing cloud photos, files and calendars and to private email on a custom domain cover the neighbouring services. Every protocol detail below comes from the Matrix specification (version v1.19 was the latest when we checked on 24 October 2026), and every setting and default from the Synapse documentation, with Synapse 1.163.2 the newest stable release, published on 23 October 2026.
Key takeaways
- Matrix is an open standard for decentralised, federated communication, stewarded by the Matrix.org Foundation, a non-profit UK Community Interest Company. Work on Matrix began in 2014; versioned spec releases started with v1.1 on 9 November 2021.
- A homeserver stores the history and account data for its own users and replicates rooms with every other homeserver that has users in them. There is no single server that owns a room.
- Synapse is the reference homeserver, written in Python and licensed AGPL-3.0-or-later (or under a commercial licence from Element) since its move to AGPL in late 2023. Dendrite, the Go server, is in maintenance mode and receives security fixes only. Several community Rust servers are listed as stable.
- Encryption uses Olm (a Double Ratchet implementation for device to device channels) and Megolm (a group ratchet for room messages), with cross-signing for identity and server-side key backup that the server cannot read. vodozemac is the Rust library that implements both.
- End-to-end encryption hides message bodies, not metadata. The server sees senders, rooms, timestamps, membership, IP addresses and unencrypted room state such as names and topics.
- Federation trusts DNS, TLS and per-server Ed25519 signing keys. Server ACLs, a federation allow list and room version upgrades are your controls when another server misbehaves.
- Bridges are application services that usually end end-to-end encryption at the bridge and hold credentials for the other network. Treat each one as privileged.
- Hardening is mostly configuration: registration closed, rate limits and media limits on, URL previews off unless you set an IP deny list, a proxy that does not rewrite URIs, and restore-tested backups.
What is Matrix?
The standard and its steward
The Matrix specification describes itself as "a set of open APIs for decentralised communication." It defines how clients talk to their homeserver (the Client-Server API), how homeservers talk to each other (the Server-Server API, also called the federation API), how bridges and bots plug in (the Application Service API), how phone push notifications are relayed (the Push Gateway API) and how optional identity servers map email addresses and phone numbers to Matrix IDs (the Identity Service API). Its stated principles include JSON over REST, fully open federation, no patent encumbrances and no single point of control.
The Matrix.org Foundation is the custodian of that standard. Its About page describes it as a non-profit UK Community Interest Company, registered as company number 11648710, incorporated "to act as the neutral guardian of the standard." Changes to the protocol go through Matrix Spec Change proposals (MSCs), and the Spec Core Team decides which ones merge. The Foundation was introduced publicly in a blog post on 29 October 2018, after a June 2018 plan for open governance.
Matrix's founders also created Element, a for-profit company that employs much of the core team, develops Synapse and runs the matrix.org homeserver for the Foundation; its November 2023 post says it drove more than 95 percent of core development over the previous seven years. So the protocol is governed by the Foundation, while licence and release decisions for the main homeserver are Element's.
A dated timeline
| Date | Event | Source |
|---|---|---|
| 2014 | Work on Matrix begins | Element AGPL post; Matrix 2.0 announcement |
| June 2018 | Plan for open governance of the protocol published | Matrix.org Foundation blog |
| 29 October 2018 | Matrix.org Foundation introduced | Matrix.org blog |
| 2019 | Matrix 1.0 takes the protocol out of beta | Matrix 2.0 announcement |
| 9 November 2021 | Spec v1.1, the first release of the versioned specification | Spec v1.1 changelog |
| 6 November 2023 | Element announces Synapse and Dendrite will move to AGPLv3 in new repositories | Element blog |
| 13 December 2023 | Synapse is AGPL and lives at github.com/element-hq/synapse | Update note on the Element blog |
| 29 October 2024 | Matrix 2.0 declared ready: Simplified Sliding Sync, native OIDC auth, MatrixRTC, invisible encryption | Matrix.org blog |
| 11 August 2025 | Coordinated security release for all server implementations; room version 12 introduced | Matrix.org blog |
| 28 July 2026 | Batch of Synapse security advisories published, several rated high | Synapse GitHub advisories |
| 23 October 2026 | Synapse 1.163.2 released; spec v1.19 is the latest version | Synapse releases; spec.matrix.org |
Homeservers, rooms, events and federation
Per the spec's architecture section, Matrix synchronises extensible JSON objects called events between clients, servers and services. A client, such as a phone or desktop chat app, synchronises with its homeserver through the Client-Server API. Each homeserver stores the communication history and account information for all of its own clients, and shares data with the wider network by synchronising history with other homeservers.
A room is a shared, replicated log of events. When Alice on example.org and Bob on example.net are both in a room, each of their homeservers holds a full copy of the room's history. If example.org goes offline, Bob's server still has the room and Bob can keep reading and posting; when Alice's server returns, the servers catch up. That replication is what the spec means by "no single point of control."
Users are named like @alice:example.org and room aliases like #general:example.org; the part after the colon is the server name, and it is permanent once users and rooms exist.
The federation API carries three kinds of traffic:
- Persistent Data Units (PDUs) are room events that become part of the room's history and are broadcast to every server in the room.
- Ephemeral Data Units (EDUs) are pushed between pairs of servers and are not stored as history: typing notifications, read receipts, presence and to-device messages.
- Queries are single request and response lookups, such as fetching a user's profile or resolving a room alias.
Rooms evolve through state events: who is a member, who has which power level, the room name and topic, the join rules and whether the room is encrypted. Every server independently decides whether an event is allowed under the room's authorisation rules, and a state resolution algorithm lets servers converge when they saw events in different orders. The exact rules depend on the room version, which is why room versions matter so much for security.
Which homeserver implementations exist?
The Matrix.org ecosystem page lists servers with a maturity rating and licence. As of 24 October 2026, the entries relevant to a self-hoster look like this.
| Server | Language | Licence (per its listing or repository) | Status | Notes |
|---|---|---|---|---|
| Synapse | Python | AGPL-3.0-or-later, or a commercial licence from Element | Stable | The reference server; official Docker image and Debian packages |
| Synapse Pro | Python and Rust | Commercial | Stable | Element's enterprise edition; not something you self-host for free |
| Dendrite | Go | AGPL (moved with Synapse in 2023) | Beta, maintenance mode | Security fixes only; lacks Simplified Sliding Sync (MSC4186) and next-gen auth (MSC3861), so current mobile clients built on them cannot use it |
| continuwuity | Rust | Apache-2.0 | Stable | Community fork in the Conduit family |
| Tuwunel | Rust | Apache-2.0 | Stable | Successor to conduwuit, also in the Conduit family |
| conduwuit | Rust | Apache-2.0 | Obsolete | Superseded by its forks |
Dendrite's README says it is "in maintenance mode, meaning only security fixes are being applied," that it is beta software without clustering, and that it suits deployments of tens or hundreds of users. A new deployment in 2026 should not start there without a specific reason.
The August 2025 coordinated security release named the implementations that shipped fixes on the same day: Conduit, Continuwuity, ejabberd, Dendrite, Rocket.chat, Synapse, Synapse Pro and Tuwunel. That list is a fair proxy for which servers coordinate with the Foundation's security team. The guide below uses Synapse, which has the fullest documentation and first-party packages; the hardening ideas carry over to the Rust servers under different setting names.
What the AGPL move means for you
On 6 November 2023, Element announced that future development of Synapse, Dendrite and related server projects (Sydent, Sygnal and the Matrix Authentication Service) would continue under the GNU Affero General Public License v3, in new repositories under the element-hq GitHub organisation, forked from the Apache-licensed repositories in the matrix-org organisation. A note added on 13 December 2023 says Synapse is now AGPL. Client-side code was not affected.
Element's post spells out the impact. For people "running unmodified free-standing open source instances" of these servers, nothing changes beyond pointing deployments at the new repositories. Organisations that modify the code and either distribute it or run it as a service must publish their modifications under the AGPL or arrange a commercial licence with Element. Contributors must now sign a contributor licence agreement based on the Apache CLA, which replaced the old DCO sign-off; Element says the sole reason is to allow dual licensing.
The current Synapse README says the code is "available under an AGPL license (or alternatively under a commercial license from Element)," with no support from Element without a subscription. It names the Element Server Suite, including a free ESS Community edition for small and mid-scale non-commercial use, as the official means of deployment; standalone installation remains documented and is what this guide uses.
If you run an unmodified Synapse for a family, a club or a small company, the AGPL asks nothing new of you. If you patch Synapse and let other people use your patched server over a network, the AGPL's network clause applies, and you should publish your changes.
How does encryption work?
Olm: one device to another
Olm is the spec's "implementation of the double cryptographic ratchet" first described for the Signal protocol. Two devices set up a channel from Curve25519 identity keys and one-time keys that each device uploads to its homeserver, and every message then advances the ratchet. In Matrix, Olm does not carry room messages; it carries to-device messages such as key shares, verification requests and secrets. That is why the one-time keys your server stores matter, a point that returns in the backup section.
Megolm: one sender to a room
Encrypting every room message separately to every device of every member would not scale. Megolm is the spec's answer: "an AES-based cryptographic ratchet intended for group communications." Each sender device creates its own outbound session, which consists of a ratchet and an Ed25519 key pair. The ratchet can be wound forwards but not backwards, and each message uses a distinct key derived from it. The sender shares the current ratchet value and public key with each recipient device over Olm. The ciphertext then goes to the room once, and every recipient with the session key can decrypt it.
The client-server spec lists the primitives behind m.megolm.v1.aes-sha2: HMAC-SHA-256 for the hash ratchet, HKDF-SHA-256, AES-256 in CBC mode with an 8 byte truncated HMAC-SHA-256 for authenticated encryption, and Ed25519 for message authenticity.
The Megolm spec is frank about the limits:
- Partial forward secrecy. A recipient keeps a ratchet value that decrypts every later message in the session. If that value is stolen, the attacker can read those messages, which is why the spec calls the forward secrecy "partial."
- No backward secrecy. Once a session key is compromised, an attacker can decrypt messages derived from that or later ratchet values. The spec tells applications not to use sessions indefinitely and to start new ones periodically, sharing fresh keys over a secure channel.
- Message replays and transcript consistency. The spec lists both as limitations that applications must handle.
The client-server spec tells clients to replace an outbound session when it has been in use longer than rotation_period_ms, when it has encrypted more messages than rotation_period_msgs (both set in the room's encryption state event, or a sensible default), or when a user or device that may hold the keys leaves the room. Rotation limits what a stolen session can read.
Cross-signing and verification
Without help, Alice would need to verify each of Bob's devices from each of hers. Cross-signing fixes that. Each user has three Ed25519 key pairs: a master signing key that acts as the user's identity, a self-signing key that signs the user's own devices, and a user-signing key that signs other users' master keys. Verify Bob once, and every device Bob signs with his self-signing key is trusted.
Verification itself usually uses the Short Authentication String method. The spec bases its key agreement phase on ZRTP and its verification phase on HMAC, and both people compare a short sequence of emoji or numbers over a channel where neither can be impersonated, such as in person or on a video call. A machine in the middle produces mismatched strings.
Key backup and secret storage
Server-side key backup stops a lost device from meaning lost history. The client encrypts each session key to a Curve25519 backup public key using the m.megolm_backup.v1.curve25519-aes-sha2 algorithm and uploads the result. The homeserver stores ciphertext it cannot read. The private key for the backup, along with the cross-signing private keys, can be kept in secret storage, which is also encrypted on the client (the m.secret_storage.v1.aes-hmac-sha2 scheme) and stored in the user's account data.
For a self-hoster this means two things. Your database holds every user's encrypted key backup, so a database leak is not a plaintext leak, but it is a target for offline guessing if a user protects secret storage with a weak passphrase. And if users lose their recovery key, you cannot help them; the server never had the key.
vodozemac
vodozemac is a pure Rust implementation of Olm and Megolm, maintained in the matrix-org GitHub organisation and published on crates.io. Its README describes it as a modern alternative to the older C library libolm, and says it also implements SAS verification and the integrated encryption scheme from MSC4108. The README records one external security audit with no significant findings. Current clients built on the Rust SDK use vodozemac, so the cryptography in a modern Matrix client is memory-safe Rust rather than C.
What stays visible to the server
End-to-end encryption in Matrix encrypts the content of message events. It does not hide the envelope, and much of the room's structure is deliberately unencrypted so that servers can enforce permissions.
| Data | Visible to your homeserver? | Why |
|---|---|---|
| Message text, images and files in an encrypted room | No | Encrypted on the client; attachments are encrypted before upload |
| That a message was sent, by whom, in which room, and when | Yes | Event metadata the server must route and store |
| Room membership, joins, leaves, invites and bans | Yes | State events the server uses for authorisation |
| Room name, topic, avatar and power levels | Yes | Unencrypted state events |
| Device lists and public device keys | Yes | Needed so other devices can encrypt to them |
| Encrypted key backups | Stored, not readable | Encrypted to a key only the user holds |
| Client IP addresses and user agents | Yes | Kept for user_ips_max_age, 28 days by default |
| Display names and avatars | Yes, and to other servers | Profile data is served over federation unless restricted |
| Read receipts and typing notifications | Yes | Unencrypted ephemeral data passed between servers |
Every server with a member in the room sees the same metadata, so open federation spreads your users' social graph to servers you do not run. You control your own server, not the servers your users talk to.
Room encryption is a state event (m.room.encryption), set when the room is created or turned on later. Synapse's encryption_enabled_by_default_for_room_type option lets an admin force encryption on new rooms (all), on private chats only (invite) or leave it to clients (off). It affects only rooms created after you set it.
How does federation work?
Finding a server
When example.net needs to talk to example.org, it resolves the server name using the algorithm in the Server-Server API. Simplified:
- If the server name is an IP literal, connect to it, on the given port or 8448.
- If the name includes an explicit port, resolve the hostname and connect there.
- Otherwise, fetch
https://example.org/.well-known/matrix/server. If it returns JSON with anm.serverkey, use that delegated host and port. This is how you keep@alice:example.orgIDs while running the server onmatrix.example.org:443. - If there is no well-known file, look up an SRV record for
_matrix-fed._tcp.example.org(added in spec v1.8). The older_matrix._tcpSRV name is deprecated but still checked. - If none of that resolves, connect to the name itself on port 8448.
In every case the target must present a valid TLS certificate for the hostname being used. Federation depends on the public certificate system, not on a Matrix-specific PKI.
The Synapse delegation guide puts it plainly: by default other servers expect to reach yours at your server_name on port 8448, and delegation lets you route that traffic somewhere else. The well-known file must be served on the default HTTPS port, 443.
Clients use a separate file, /.well-known/matrix/client, which tells an app where the Client-Server API lives. You usually serve both from the web server for your bare domain.
Signing keys and notaries
Every homeserver has Ed25519 signing keys. It signs every event it creates and every federation request it sends, using an Authorization: X-Matrix header, and publishes its public keys at /_matrix/key/v2/server. Each published key list carries a valid_until_ts, and the spec says servers must use the lesser of that value and seven days into the future, so an attacker who publishes a long-lived key cannot make it stick indefinitely.
Servers can fetch another server's keys directly or through a notary that fetches them and countersigns the response. The spec credits the idea to the Perspectives Project: each server picks which notaries it trusts and can cross-check them. Synapse's default trusted_key_servers entry is matrix.org. For a private federation, or if you would rather not tell matrix.org which servers you talk to, set trusted_key_servers: [], and Synapse will fetch keys directly from each server.
The signing key is your server's identity. Lose it and you can generate a new one, listing the old public key under old_signing_keys; leak it and someone else can sign as your server until you rotate.
Server ACLs
A room can ban whole servers. Sending an m.room.server_acl state event, in the spec's words, is "an effective way to prevent the server from participating in the room at the federation level." ACLs hold glob patterns in allow and deny lists, ignore port numbers, and can also restrict a room to a fixed set of servers. Every participating server is expected to enforce the ACL, and servers that do not should themselves be added to the deny list. ACLs are a room setting that room moderators control, not a server setting.
Room versions
The room version fixes the event format, the authorisation rules and the state resolution algorithm. New room versions fix protocol-level problems, and existing rooms only get the fix when a moderator upgrades them, which creates a new room and points the old one at it with a tombstone event.
Room version 12 is the clearest recent example. On 11 August 2025 the Foundation shipped a coordinated release across server implementations to fix two high severity protocol vulnerabilities, and introduced room version 12. In version 12, room creators are privileged over other users, a new additional_creators field exists on the create event, the default power level for sending the tombstone event that upgrades a room is 150 (so ordinary admins cannot upgrade a room and take creator rights), and room IDs become hashes of the create event. The Foundation's advice was proportionate: a single, unfederated server had nothing urgent to do; servers in a fully trusted restricted federation could wait; servers in open federation should update as soon as possible, and rooms that federate with untrusted servers should plan an upgrade, ideally performed by a long-lived trusted account such as a moderation bot.
Abuse and moderation risks that come with federation
Open federation makes Matrix useful and makes you inherit other people's problems:
- Your server stores other servers' content. Join a large public room and your server replicates its history and fills its remote media cache with material posted by strangers.
- Spam and invite floods. Open registration on any server lets bad actors create accounts that then reach your users through invites and direct messages. The Synapse docs call registration without verification "a known vector for spam and abuse."
- Hostile or broken servers. A malicious server can send malformed events or flood the federation API. Room versions and ACLs are the protocol's defences; patching is yours.
- Domain takeover. The spec's own security considerations warn that when a domain changes owner, the new owner can masquerade as the old server, receive messages and request past history from other servers. If you retire a homeserver, keep the domain.
- Moderation is per room and per server. No central authority removes a bad server from the network. Moderators use ACLs, bans, power levels and shared ban lists maintained by moderation bots. The spec also now defines Policy Servers, an optional tool that rooms can enable so a designated server signs off on events before other servers accept them.
What do bridges and application services do?
The Application Service API
A bridge is a program that connects Matrix to another network, and in Matrix terms it is an application service. The Application Service API lets a service register with a homeserver using a YAML file that contains an as_token (which the service uses to call the homeserver), an hs_token (which the homeserver uses to call the service), a unique id, and namespaces of user IDs, room aliases and room IDs that the service is interested in. In Synapse, you list those files under app_service_config_files.
Namespaces can be exclusive. The spec explains that an exclusive namespace "prevents humans and other application services from creating/deleting entities in that namespace," which is how a bridge reserves, say, every user ID that starts with @telegram_. The homeserver then pushes every event in matching rooms to the service, and the service can act as any user in its namespace.
That is a lot of power. An application service with a leaked as_token can create users and post as any of them within its namespace. Protect registration files like credentials, keep them readable only by the homeserver and the bridge, and remove services you no longer run.
What bridging costs in privacy
The Matrix.org bridges page lists bridges for many networks, including Discord, Slack, Signal, Telegram, WhatsApp, IRC, XMPP, email and SMS. Each one changes the privacy properties of the conversation:
| Concern | What happens | What to do |
|---|---|---|
| End-to-end encryption | A bridge must read messages to translate them. In an encrypted Matrix room, the bridge decrypts on your server; on the other network, encryption is whatever that network does | Treat the bridge host as holding plaintext; run it on the same trusted machine as the homeserver |
| Credentials | Puppeting bridges log in to the other network as you and keep that session or token | Store bridge data on encrypted disks, back it up, restrict access |
| Metadata | Your Matrix room's membership and activity become visible to the other network's operator | Do not bridge rooms whose membership is sensitive |
| Terms of service | Some networks forbid unofficial clients and may ban accounts | Read the other network's terms before bridging personal accounts |
| Software supply chain | Bridges are separate projects with their own dependencies and release cadence | Pin versions, follow each bridge's releases, rebuild images |
| Federation exposure | Bridged users appear as Matrix users on your server and can be seen by other servers in shared rooms | Keep bridged rooms local when possible |
Bridges make Matrix a hub where every network's messages exist in plaintext at least briefly. That is fine when the hub is yours alone, and deserves thought when you run it for others.
How do you harden a homeserver?
Reverse proxy and TLS
Synapse listens for plain HTTP on port 8008 by default and expects a reverse proxy in front to terminate TLS. The reverse proxy guide has two warnings that cause most broken deployments:
- Your proxy "must not canonicalise or normalise the requested URI in any way." Federation requests are signed, and if the proxy decodes escapes or adds a trailing path, signature verification fails. For nginx, that means no path after the port in
proxy_pass; for Apache, it meansnocanon. - nginx allows only 1 MB request bodies by default. Set
client_max_body_sizeto match Synapse'smax_upload_size, which defaults to 50M.
Set x_forwarded: true on the Synapse listener so it records real client IPs, and bind the listener to 127.0.0.1 or ::1 so nothing reaches Synapse except through the proxy. For outbound federation, federation_client_minimum_tls_version defaults to "1"; the docs warn that raising it above "1.2" will break federation with most of the public network, so set "1.2" for an open server and "1.3" only for an entirely private federation.
Registration controls
enable_registration defaults to false. Leave it that way unless you are running a public service. Options when you do need sign-ups:
registration_requires_token: truewith tokens issued through the admin API, so only people you invite can register.- Email verification or a CAPTCHA. Do not set
enable_registration_without_verification, which the docs call "not recommended." registration_shared_secret, which lets theregister_new_matrix_userscript create users even when registration is closed. The docs warn that anyone with this secret can create admin accounts, so treat it like a root password, and remove it from the config after creating your first accounts if you do not need it.
Keep allow_guest_access at its default of false, and keep allow_public_rooms_without_auth and allow_public_rooms_over_federation at their defaults of false unless you want an open room directory.
Rate limits
Synapse ships with sensible rate limits. rc_message defaults to 0.2 messages per second with a burst of 10. rc_registration defaults to 0.17 requests per second with a burst of 3, per IP address. rc_login has separate limits by address, by account and by failed attempts per account. Tighten them under abuse, and exempt your own bots through the admin API rather than raising limits for everyone.
Media repository limits
The media repository is the part of a homeserver most likely to fill your disk or fetch something it should not.
max_upload_size(50M by default) andmax_image_pixels(32M by default) cap upload and thumbnailing work.- URL previews are off by default (
url_preview_enabled: false). If you turn them on, Synapse requires you to seturl_preview_ip_range_blacklist, because the preview spider fetches arbitrary URLs and could otherwise reach your internal network. Include every private, loopback and link-local range. ip_range_blacklistapplies to other outbound requests: federation, identity servers and push. If you do not set it, Synapse blocks private ranges by default.enable_authenticated_medianow defaults to true, so new uploads are served only to authenticated clients over the newer media endpoints rather than to anyone with the URL.media_retentionhaslocal_media_lifetimeandremote_media_lifetimeoptions that purge media nobody has accessed for a set time, which bounds both disk use and how long you hold other servers' content.prevent_media_downloads_fromlets you refuse to cache media from named servers.
Privacy settings for the server itself
require_auth_for_profile_requests: trueandlimit_profile_requests_to_users_who_share_rooms: truestop strangers from enumerating profiles through the client API.allow_profile_lookup_over_federation: falsedoes the same for other servers.user_ips_max_agedefaults to 28d; shorten it if you do not need IP history.redaction_retention_periodcontrols how long the original content of redacted messages stays in the database. Shorten it if users expect deletes to mean deletes.
Signing key and database backups
The Synapse backup guide lists what you need: the configuration directory (including app service registration files and the log config), the server signing key, the PostgreSQL database and the media store's local_content directory, which "may represent the only copy of those media files anywhere in the federation."
It adds two database rules that are easy to get wrong:
- Do not restore into a database that already has tables. "At best, this will error; at worst, it will lead to subtle database inconsistencies."
- Exclude the
e2e_one_time_keys_jsontable from backups, or truncate it after restoring. Restoring old one-time keys causes them to be handed out twice, which breaks decryption for messages encrypted with them.
The docs also say SQLite is only suitable for testing, so production means PostgreSQL.
Following security advisories
The Matrix.org security disclosure policy follows coordinated disclosure: reporters email the security team, receive a reply within five business days, and the Foundation aims for a fix within 90 days (120 for especially complex issues), sometimes delaying technical details by up to 30 days after a fix ships. The August 2025 release shows the pattern: a pre-disclosure notice to server admins, then a fixed release at a set time.
Synapse publishes its advisories on its GitHub repository. On 28 July 2026 the repository published a batch of advisories, five of them rated high, covering memory exhaustion from crafted multipart requests, federation endpoints that could disclose events from rooms the requesting server was not in, to-device messages spoofed to look like they came from another server, and resource exhaustion from user-configured push rules. Subscribe to Synapse releases, read the upgrade notes before each upgrade, and keep Python and PostgreSQL on supported versions, because the upgrade guide says Synapse drops versions upstream no longer maintains.
Step by step: Synapse on PostgreSQL with delegation, closed registration and tested backups
This walkthrough assumes a Debian or Ubuntu amd64 server, a domain example.org whose user IDs you want to keep, and a host matrix.example.org that will run Synapse. Replace the names with yours.
1. Install Synapse from the Matrix.org packages
The installation guide recommends prebuilt packages for most users. Matrix.org publishes Debian and Ubuntu packages for amd64, signed with a repository key.
sudo apt install -y lsb-release wget apt-transport-https
sudo wget -O /usr/share/keyrings/matrix-org-archive-keyring.gpg \
https://packages.matrix.org/debian/matrix-org-archive-keyring.gpg
# Check the fingerprint before trusting the key.
gpg --show-keys /usr/share/keyrings/matrix-org-archive-keyring.gpg
# Expected: AAF9AE843A7584B5A3E4CD2BCF45A512DE2DA058
echo "deb [signed-by=/usr/share/keyrings/matrix-org-archive-keyring.gpg] https://packages.matrix.org/debian/ $(lsb_release -cs) main" \
| sudo tee /etc/apt/sources.list.d/matrix-org.list
sudo apt update
sudo apt install matrix-synapse-py3
Set the server name to example.org, not matrix.example.org, when asked. That choice is permanent. The main config file is /etc/matrix-synapse/homeserver.yaml; the docs suggest putting your own settings in files under /etc/matrix-synapse/conf.d/ so package upgrades do not prompt you about replacing the main file.
If you prefer containers, the docs point to the official image at ghcr.io/element-hq/synapse (also on Docker Hub as matrixdotorg/synapse). Pin a version tag rather than latest, so an upgrade is a decision you make after reading the release notes.
2. Create the PostgreSQL database
The database must use UTF8 encoding and the C locale, created from template0.
sudo apt install -y postgresql
sudo -u postgres createuser --pwprompt synapse_user
sudo -u postgres createdb --encoding=UTF8 --locale=C \
--template=template0 --owner=synapse_user synapse
Then point Synapse at it in /etc/matrix-synapse/conf.d/database.yaml:
database:
name: psycopg2
args:
user: synapse_user
password: "use-a-long-random-password"
dbname: synapse
host: localhost
cp_min: 5
cp_max: 10
Keep the database on the same host or a private network. Do not expose port 5432.
3. Bind Synapse to localhost and set the hardening defaults
Create /etc/matrix-synapse/conf.d/hardening.yaml:
listeners:
- port: 8008
tls: false
type: http
x_forwarded: true
bind_addresses: ['::1', '127.0.0.1']
resources:
- names: [client, federation]
compress: false
public_baseurl: https://matrix.example.org/
# Registration stays closed. Create accounts as an admin.
enable_registration: false
allow_guest_access: false
# Profile and directory privacy
require_auth_for_profile_requests: true
limit_profile_requests_to_users_who_share_rooms: true
allow_public_rooms_without_auth: false
allow_public_rooms_over_federation: false
# Encrypt new private chats by default
encryption_enabled_by_default_for_room_type: invite
# Media
max_upload_size: 50M
url_preview_enabled: false
enable_authenticated_media: true
media_retention:
remote_media_lifetime: 90d
# Shorter IP retention
user_ips_max_age: 14d
# Outbound federation TLS floor that still works with the public network
federation_client_minimum_tls_version: "1.2"
Check each key against the configuration manual for your version, then restart and watch the log:
sudo systemctl restart matrix-synapse
sudo journalctl -u matrix-synapse -n 50 --no-pager
curl -s http://127.0.0.1:8008/_matrix/client/versions | head -c 300
4. Put a TLS reverse proxy in front
Get a certificate for matrix.example.org from any ACME certificate authority, then configure nginx along the lines of the Synapse example. The comments matter.
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name matrix.example.org;
ssl_certificate /etc/ssl/matrix/fullchain.pem;
ssl_certificate_key /etc/ssl/matrix/privkey.pem;
location ~ ^(/_matrix|/_synapse/client) {
# No path after the port: nginx would canonicalise the URI
# and break federation signature checks.
proxy_pass http://localhost:8008;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $host:$server_port;
# Match max_upload_size in Synapse.
client_max_body_size 50M;
proxy_http_version 1.1;
}
}
Because the next step delegates federation to port 443, you do not need to open 8448. Do not proxy /_synapse/admin to the internet; reach the admin API from localhost or over a VPN.
5. Set up .well-known delegation on the bare domain
On the web server for example.org, serve two small JSON files:
location = /.well-known/matrix/server {
default_type application/json;
return 200 '{"m.server": "matrix.example.org: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.org"}}';
}
Check them and test federation:
curl -s https://example.org/.well-known/matrix/server
curl -s https://example.org/.well-known/matrix/client
curl -s https://matrix.example.org/_matrix/key/v2/server | head -c 400
The Synapse federation guide points to the public federation tester for a full check. Its typical failure mode is a "401: Unauthorized" when joining a remote room, which usually means other servers cannot reach yours.
6. Create the first admin without opening registration
register_new_matrix_user needs registration_shared_secret set in the config. Set it temporarily, create your account, then remove it and restart.
# In a conf.d file, temporarily:
# registration_shared_secret: "<long random value>"
sudo systemctl restart matrix-synapse
register_new_matrix_user -c /etc/matrix-synapse/conf.d/secret.yaml http://localhost:8008
# Answer the prompts, choose "yes" for admin on your own account only.
sudo rm /etc/matrix-synapse/conf.d/secret.yaml
sudo systemctl restart matrix-synapse
For later accounts, either repeat this, or enable registration_requires_token: true alongside enable_registration: true and issue single-use tokens through the admin API.
7. Restrict federation if you need to
For a fully private server, set an empty allow list. The Synapse manual says an empty list "is the recommended way of disabling federation."
federation_domain_whitelist: []
trusted_key_servers: []
For a small trusted group of servers, list them instead, and firewall inbound federation to their addresses as well, since the docs recommend not relying "purely on this application-layer restriction":
federation_domain_whitelist:
- example.org
- partner.example.net
trusted_key_servers: []
A closed or restricted federation also changes your patch urgency. The Foundation's August 2025 guidance told single, unfederated servers they had nothing urgent to do for that release, and told restricted federations to patch quickly only if they did not fully trust every member.
8. Back up the keys and the database
Find your signing key path first; on Debian packages it lives under /etc/matrix-synapse/.
sudo grep -R "signing_key_path" /etc/matrix-synapse/
sudo ls -l /etc/matrix-synapse/*.signing.key
Then take the backup. pg_dump takes a consistent snapshot without stopping Synapse.
BACKUP=/srv/backup/matrix/$(date +%F)
sudo mkdir -p "$BACKUP"
sudo -u postgres pg_dump -Fc --exclude-table-data e2e_one_time_keys_json synapse \
| sudo tee "$BACKUP/synapse.dump" > /dev/null
sudo tar -C /etc -czf "$BACKUP/matrix-synapse-etc.tar.gz" matrix-synapse
sudo tar -C /var/lib/matrix-synapse/media -czf "$BACKUP/local_content.tar.gz" local_content
sudo sha256sum "$BACKUP"/* | sudo tee "$BACKUP/SHA256SUMS"
The tar of /etc/matrix-synapse contains the signing key and any secrets in your config. Encrypt the backup before it leaves the machine, and keep at least one copy off site.
9. Test a restore on a separate machine
Warning: pg_restore into a database that already has tables can corrupt it, and the Synapse docs say never to restore over an existing database. Run this only on a scratch host or a fresh database, and check which host and database you are connected to before each step.
# Confirm you are on the scratch host, not production.
hostname
sudo -u postgres psql -Atc "SELECT datname FROM pg_database WHERE datname = 'synapse';"
# The query must return nothing. If it prints "synapse", stop.
sudo -u postgres createdb --encoding=UTF8 --locale=C \
--template=template0 --owner=synapse_user synapse
sudo -u postgres pg_restore -d synapse < synapse.dump
# Only if the dump included one-time keys:
sudo -u postgres psql -d synapse -c "TRUNCATE e2e_one_time_keys_json;"
Restore the config and media archives, start Synapse with networking restricted so it cannot federate as your production server, and confirm that you can log in and read room history from an existing client session. A backup you have never restored is a guess.
How does self-hosted Matrix compare with the alternatives?
| Self-hosted Matrix | Self-hosted XMPP | Hosted end-to-end encrypted messenger | ||
|---|---|---|---|---|
| Standard | Matrix spec, Matrix.org Foundation | IETF RFC 6120 (core, March 2011) plus extensions | Usually proprietary protocol, sometimes open source clients | SMTP and related IETF standards |
| Federation | Yes, open by default, can be restricted | Yes, server to server | No; one operator | Yes |
| Room history | Replicated to every participating server | Stored per server; group chat history depends on extensions | Held on devices, sometimes in operator backups | Stored on each recipient's server |
| End-to-end encryption | Olm and Megolm in the spec; enabled per room, and a server setting can force it on new private chats | Via extensions such as OMEMO (XEP-0384, Experimental status) | On by default in the services designed for it | Optional (OpenPGP or S/MIME), rarely used |
| Metadata your server sees | Senders, rooms, membership, timestamps, room names and topics | Rosters, presence, message routing | Operator sees what its design exposes; you cannot inspect the server | Headers, subjects (unless encrypted), routing |
| Metadata other servers see | All of the above for shared rooms | Routing for cross-server messages | None outside the operator | Headers and subjects for every message they receive |
| Admin effort | Moderate to high: database, media, federation, upgrades | Moderate: lighter server, more extension choices | None | High if you run your own mail server |
| Bridging | Rich application service ecosystem | Gateways exist, fewer | Usually none | Gateways to chat exist |
If your goal is the smallest metadata footprint against an operator you do not trust, a hosted messenger built for minimal metadata can beat self-hosted Matrix, which replicates room metadata by design. If your goal is control, interoperability and no single company in the middle, self-hosted Matrix fits, and a closed federation recovers much of the metadata privacy.
Which deployment fits you?
| Situation | Sensible choice |
|---|---|
| A family or small team who only talk to each other | Synapse, federation disabled, registration closed |
| A group of organisations that trust each other | Synapse with a federation allow list and firewall rules; trusted_key_servers empty |
| A community that wants to join public rooms | Open federation, registration by token, media retention, a moderation bot and fast patching |
| Low-memory hardware, a handful of users | One of the stable Rust servers, after checking client compatibility |
| Regulated or large deployment | A supported distribution with a commercial licence, or the full Synapse worker setup |
| You only want to chat on Matrix | Use an existing homeserver; you do not need to run one |
Common mistakes
- Using the subdomain as the server name. Choosing
matrix.example.orgasserver_namegives users IDs like@alice:matrix.example.orgforever. Use the bare domain and delegate with .well-known. - Leaving SQLite in production. The docs call it suitable only for testing. Migrating later is possible but painful.
- A reverse proxy that rewrites URIs. A trailing slash in
proxy_passor a missingnocanonin Apache breaks federation signatures in ways that look like random 401 errors. - Opening registration without verification. It is the most common route to a server full of spam accounts that then gets blocked by other servers.
- Enabling URL previews without an IP deny list. The preview spider will fetch internal addresses if you let it.
- Exposing the admin API. The reverse proxy guide says it does not recommend exposing
/_synapse/adminto the public internet "without good reason," because those endpoints grant the caller a lot of power. - Restoring a database dump over an existing database, or restoring one-time keys. Both are called out in the backup guide for good reason.
- Assuming encryption hides everything. Room names, topics and membership are visible to every server in the room.
- Running bridges with default permissions and old images. A bridge is a privileged user factory with its own dependencies.
- Not upgrading rooms after a new room version fixes a security issue. Patching the server does not change old rooms' rules.
FAQ
Is a self-hosted Matrix server more private than a hosted messenger?
It depends on the threat. You control your own server's data and logs, but in federated rooms every participating server sees the same metadata and room state is unencrypted. A self-hosted server with federation disabled gives you both control and a small footprint.
Does Matrix encrypt messages by default?
The protocol supports end-to-end encryption through Olm and Megolm, and encryption is switched on per room by a state event. Clients decide whether to set it when a room is created, and large public rooms are often left unencrypted because anyone can join. An admin can force it on new rooms with Synapse's encryption_enabled_by_default_for_room_type setting.
Can my homeserver admin read my messages?
In encrypted rooms, no: the server stores ciphertext and encrypted key backups it cannot decrypt. In unencrypted rooms, yes. In both cases the admin sees metadata: who is in which room, when messages were sent, IP addresses and room names.
What happened to Synapse's licence?
On 6 November 2023 Element announced that Synapse and Dendrite would continue under AGPLv3 in new repositories, and Synapse became AGPL that December. It is available under AGPL-3.0-or-later or a commercial licence from Element. Running an unmodified server needs nothing new; modifying it and offering it to others means publishing your changes or buying a licence.
Is Dendrite still a good choice?
For new deployments, generally not. Its README says it is in maintenance mode with only security fixes, is beta software, has no clustering and does not support Simplified Sliding Sync or the next-generation auth that newer clients need.
Do I need port 8448 open?
No, if you delegate federation with /.well-known/matrix/server or an SRV record to port 443. Without delegation, other servers connect to your server name on port 8448.
Are Matrix bridges safe?
A bridge usually decrypts messages to pass them on, stores credentials for the other network and can act as many users. Run only the bridges you need, on hardware you control, and update them like any internet-facing software.
How do I hear about Matrix security fixes?
Watch Synapse releases and its GitHub security advisories, read the Matrix.org blog's security category, and join your server implementation's announcement room. The Foundation pre-announces coordinated releases, as it did before the 11 August 2025 fix.
Where Safeguard fits
Safeguard does not run, monitor or moderate Matrix homeservers, and it does not inspect your federation traffic, encryption keys or messages. It is a software supply chain security platform, and the fit here is indirect. What it can do is look at the software you deploy: safeguard scan --image ghcr.io/element-hq/synapse:v1.163.2 lists the components in the Synapse image you run and matches them against vulnerability databases, and safeguard scan --dir . does the same for a bridge or bot repository you maintain. Results land as JSON under ./output, and the platform shows which of your projects contain a given component and version. That tells you about known vulnerabilities in those components; it does not test your configuration.
A homeserver is a stack of open source parts: Synapse and its Python and Rust dependencies, PostgreSQL, a reverse proxy, each bridge with its own dependency tree, and container base images under all of them. When a vulnerability lands in one of those parts, the approaches below apply to the pieces you build and ship yourself.
Fix it without waiting for upstream
Whatever the vulnerability, most of the risk sits in two gaps. The first is the time between a fix being published and that fix actually running in your production builds. The second, and worse, is the time before any fix exists at all, while a maintainer works out a patch and a release schedule. Waiting through both gaps is a habit, not a requirement. Safeguard gives teams three ways to close them.
1. Autonomous remediation: pull requests, not tickets
When a fixed version exists, Safeguard's Griffin models open a tested, policy gated pull request that moves the affected dependency to the safe version. You can run it on one repository or in bulk across every project that carries the vulnerable version. Remediation covers eight categories, including dependencies, licenses, security posture and findings. Reachability analysis ranks the vulnerable code paths your application actually calls first, so the first pull requests you merge are the ones that matter most. Autonomous remediation ships switched off, with every category set to Manual, so your team decides how much runs on its own and what still needs a human approval.
2. Gold Open Source: the safe version in seconds, free
gold.safeguard.sh is free and needs no login. Search any CVE or package to see affected and fixed versions, CVSS, EPSS, CISA KEV status and a recommended safe version, or use the "Am I affected?" checker. The same data is available through a free read API, README status badges and RSS threat feeds. The safeguard-sh/gold-scan-action GitHub Action can fail a build on a known exploited vulnerability, on any known vulnerability, or never, so a version you have just removed cannot quietly come back in next week's merge.
3. Gold Registry: start from components that are already fixed
registry.safeguard.sh provides hardened, drop in replacements for upstream packages and container images. Every Gold artifact is held continuously to zero exploitable critical or high CVEs, zero malware and a completed license check, with SLSA L3 or higher provenance, a Sigstore signature and a public evidence page you can hand to an auditor. An artifact loses its Gold badge if it regresses. Teams can point their own package directory at the registry so hardened equivalents resolve without changing how developers install anything.
When upstream has no fix yet, or will not ship one, Safeguard first works to get the fix accepted upstream. If the issue is severe or upstream will not move, Safeguard builds a fork, fixes the vulnerability there and publishes a hardened Gold build, so you are not stuck on a maintainer's release schedule. You can also request a hardened, compatibility tested build of a specific package. This fork and patch flow is in early access today, so ask which packages it covers before you plan around it.
| Your situation | What to use |
|---|---|
| A fixed version exists | Autonomous remediation opens the upgrade pull request; Gold Open Source confirms the safe version |
| You need to know today whether you are affected | Gold Open Source lookup and "Am I affected?" checker, free and without login |
| You want vulnerable versions to stop shipping | The Gold scan GitHub Action in CI, plus Safeguard policies and gates |
| Upstream has no fix yet, or will not ship one | A custom Gold request; fork and patch (early access) |
| You want to start clean on new projects | Pull hardened components and images from Gold Registry |
One honest limit: when the vulnerable code lives in a vendor appliance, a hosted service or an operating system runtime you do not build yourself, the fix still has to come from that vendor or distribution. The steps above apply to the open source dependencies and container images you build and ship, which is where most teams carry the largest and least tracked share of their exposure.
See how Safeguard would help
These demos show Safeguard working on software projects, not on a chat server. They apply to the bridges, bots and images around a homeserver.
The first demo is a real Safeguard CLI session. After install, safeguard opens the interactive terminal, you ask it to fix the lodash CVE in a repository, and it reads the finding and advisory, asks before each command, edits package.json to lodash 4.17.21, runs npm install and npm test with your approval, prints a summary and pushes nothing. The same ask, approve and test loop fits a JavaScript or Python bridge repository whose pinned dependencies need a bump.
Click through it at your own pace, or open it full screen.
The second demo sets up the local runner on your own machine, approves it with its device code, and then scans from the terminal. For a self-hoster, that means the scan of your Synapse image or bridge code runs on hardware you control.
Click through it at your own pace, or open it full screen.
The third demo connects Claude to the Safeguard MCP server with OAuth and shows how you choose which tools Claude can use. If you already ask an assistant to help with homeserver configuration, this is how you would give it read access to your scan results and nothing more.
Click through it at your own pace, or open it full screen.
Get the apps at safeguard.sh/download and read the CLI docs at docs.safeguard.sh/docs/cli.
Quick check
Why should you exclude the e2e_one_time_keys_json table from a Synapse database backup?
Because restoring old one-time keys lets the server hand out keys that were already used, which causes decryption errors for messages encrypted with them. The Synapse backup guide says to exclude the table from pg_dump, or truncate it after restoring and before starting Synapse.
A user's messages are end-to-end encrypted. What can the homeservers in the room still see?
Who sent each event, in which room and when, the room's membership, its name, topic and power levels, device lists, and the client IP addresses on each user's own server. Encryption covers message content, not the envelope or the room state.
Sources
- Matrix Specification (latest, v1.19)
- Matrix Specification v1.1 changelog
- Matrix Server-Server API
- Matrix Client-Server API
- Matrix Application Service API
- Matrix room versions
- Matrix room version 12
- Olm: A Cryptographic Ratchet
- Megolm group ratchet
- Matrix.org Foundation: About
- Matrix.org blog: Introducing the Matrix.org Foundation (29 October 2018)
- Matrix.org blog: Matrix 2.0 Is Here (29 October 2024)
- Matrix.org blog: Security Release (11 August 2025)
- Matrix.org Security Disclosure Policy
- Matrix.org ecosystem: Servers
- Matrix.org ecosystem: Bridges
- Element blog: A new home and license (AGPL) for Synapse and friends (6 November 2023)
- Synapse repository README
- Synapse releases
- Synapse security advisories
- Synapse documentation: Installation
- Synapse documentation: Using Postgres
- Synapse documentation: Configuration manual
- Synapse documentation: Delegation
- Synapse documentation: Reverse proxy
- Synapse documentation: Federation
- Synapse documentation: Backups
- Synapse documentation: Upgrading
- Matrix Authentication Service documentation
- Dendrite repository README
- vodozemac repository README
- RFC 6120: XMPP Core
- XEP-0384: OMEMO Encryption
Last reviewed: 24 October 2026. Synapse ships frequently and renames options over time; check the configuration manual for the version you run before applying any setting above.