Short answer: a Matrix room is not stored on one server. Every homeserver with at least one joined user keeps its own full copy of the room's event graph and state. With end-to-end encryption on, those servers cannot read message content, but they all store who is in the room, who sent each event and when, how events relate to each other, and the room's state, including its name, topic and avatar. This post walks through how that replication works and exactly what each server holds.
Matrix is one of the most important open communication projects, and federation is the reason it exists: anyone can run a server and still talk to everyone else. The trade-off is that a room's metadata lives on every server that takes part. This post sticks to the specification (Matrix spec v1.19, room version 12) and says plainly where behaviour is left to each server implementation.
How Matrix Federation Works
Each Matrix user belongs to a homeserver, the server in their user ID (@alice:example.org lives on example.org). Clients talk only to their own homeserver. Homeservers talk to each other over the server-server API, exchanging two kinds of data:
- PDUs (persistent data units): room events such as messages, membership changes and state changes. These are signed, hashed and stored permanently as part of the room's history.
- EDUs (ephemeral data units): typing notifications, read receipts, presence, device list updates and to-device messages. The spec says EDUs "are not persisted and are not part of the history of a room".
There is no central server that owns a room. A room ID in room version 12 no longer even contains a server name: it is a hash of the room's m.room.create event. Every server that has a joined user is an equal participant with its own copy.
Rooms Are Replicated Event Graphs
A room's history is a directed acyclic graph (DAG) of events. Each event lists:
prev_events: the most recent events its server knew about when creating it (up to 20). These edges form the graph.auth_events: the state events that prove the sender was allowed to send it, such as the create event, the power levels and the sender's own join.depth,origin_server_ts,sender,room_id, a content hash and the origin server's signature.
Because two servers can create events at the same moment without seeing each other's, the history is a graph, not a single line. When a server sends a new event, it is pushed to every other server in the room, and each one checks the hash, the signature and the authorisation rules before storing it. This is what makes Matrix resilient: if the server that created a room goes offline, the others still hold the full room and keep talking.
It is also why each server needs to see a lot. To authorise an event, a server must know the room's membership and power levels at that point in the graph. That is a design requirement of decentralised authorisation, not an oversight.
What Happens When a New Server Joins
Say a room has users on a.example, and Bob on b.example joins for the first time. Bob's server has no copy of the room yet, so it runs a join handshake with a server that is already in it (a "resident" server):
- make_join:
b.exampleasks the resident server for a join event template and the room version. - send_join:
b.examplefills in and signs Bob's join event and sends it back. The response containsstate, "the resolved current room state prior to the join event", andauth_chain, every event needed to authorise that state. Since spec v1.6 a server may ask for membership events to be omitted at first (omit_members) to make large joins faster, and fetch them afterwards. - New events: from then on,
b.examplereceives every new event in the room as it happens. - Backfill: if Bob scrolls up past the moment his server joined,
b.examplecalls/backfillon another server to fetch older events, and/get_missing_eventsto fill gaps in the graph. Whether Bob's client can then show that older history depends on the room's history visibility setting.
So the moment one user on a new server joins, that server gets the full current state of the room (every member, the name, topic, avatar, power levels and rules) plus the auth chain behind it, and it can pull older history on demand.
State Resolution: How Servers Agree
Room state is the set of current state events, keyed by event type and state key: one m.room.name, one m.room.power_levels, one m.room.member per user, and so on. When the graph forks, two branches may disagree, for example one branch bans a user while the other promotes them.
State resolution is the deterministic algorithm every server runs to merge those branches into the same answer without talking to each other. Room version 2 introduced state resolution v2, which splits state into an unconflicted part (every branch agrees) and a conflicted part, then replays "power events" (power level changes, join rule changes, kicks and bans) in a fixed order with full authorisation checks before applying everything else. Room version 12 iterates on it: auth checks now start from an empty state map, and a "conflicted state subgraph" is included so that the events needed to authorise conflicting changes are always considered. These changes, known as state resolution v2.1, came out of Project Hydra, a 2025 effort to fix "state resets" (CVE-2025-49090), and shipped in Matrix 1.16. Existing rooms only get them after upgrading to room version 12.
The point for this post: state resolution only works if every server can read the state events it is resolving. That is the main reason room state has been unencrypted in Matrix, and why proposals to encrypt it leave the security-critical events in the clear.
What Every Participating Server Stores
Here is what one encrypted message looks like to every homeserver in the room. The values are illustrative; the field names come from the room version 12 event format and the Megolm event format.
// What every participating homeserver stores for one encrypted message
// (fields per the room version 12 PDU format and the Megolm event format)
{
"type": "m.room.encrypted",
"room_id": "!31hneApxJ_1o-63DmFrpeqnkFfWppnzWso1JvH3ogLM",
"sender": "@alice:example.org",
"origin_server_ts": 1793088000000,
"depth": 4182,
"prev_events": ["$Rqnc-F-dvnEYJTyHq_iKxU2bZ1CI92-kuZq3a5lr5Zg"],
"auth_events": ["$create...", "$power_levels...", "$alice_join..."],
"content": {
"algorithm": "m.megolm.v1.aes-sha2",
"session_id": "X3lUlvLELLYxeTx4yOVu6UDpasGEVO0Jbu+QFnm0cKQ",
"sender_key": "...", // deprecated since v1.3, still sent
"device_id": "...", // deprecated since v1.3, still sent
"ciphertext": "AwgAEnACgAkLmt6qF84IK++J7UDH2Za1YVchHyprqTqsg..."
},
"hashes": { "sha256": "..." },
"signatures": { "example.org": { "ed25519:a_key": "..." } }
}
The ciphertext hides the message type and content. Everything around it, who sent it, in which room, when, from which device, and where it sits in the graph, is readable by every participating server and kept as long as that server keeps the room.
| Data | Stored on every participating server? | Encrypted with E2EE? |
|---|---|---|
| Message text, file captions, and the keys to encrypted attachments | Yes, as ciphertext | Yes (Megolm) |
| The real event type of a message (text, image, poll, and so on) | Yes, as ciphertext | Yes: servers only see m.room.encrypted |
| Sender user ID, room ID, timestamp, depth, prev_events and auth_events of every event | Yes | No |
| Megolm session ID, and the sender's device ID and key (deprecated fields, still sent) | Yes | No |
| Membership: joins, leaves, invites, kicks, bans, with display name and avatar URL | Yes | No |
| Room name, topic and avatar | Yes | No, unless a client supports the experimental encrypted state proposals (MSC3414, MSC4362) |
| Power levels, join rules, history visibility, server ACLs, encryption settings | Yes | No: servers need these to authorise events |
| Reply, edit and thread links (m.relates_to) | Yes | No: the link is cleartext, the new content is encrypted |
| Reactions, including the emoji | Yes | No: the spec says reactions are normally not encrypted |
| Read receipts (m.read) | Sent to the room's servers as EDUs, not part of the room graph; whether they are kept is up to each server | No. Private receipts (m.read.private) are not shared |
| Typing notifications | Sent as EDUs, not persisted in the room graph | No |
| Encrypted files and images | No: on the uploader's server, plus any server whose users download them | Yes (AES-CTR, key inside the encrypted event) |
| Room key sharing between devices (to-device messages) | No: only the sending and receiving servers | Content yes (Olm); sender and recipient device no |
| Device IDs and public device keys of members | Sent to every server that shares a room with the user | No (public keys) |
| Redacted events | Yes, as a stripped skeleton | No: sender, room, timestamp and graph links survive redaction |
Sources: Server-Server API, Client-Server API and Room Version 12, Matrix specification.
What End-to-End Encryption Hides, and What It Doesn't
Encrypted rooms use Megolm for room messages and Olm to deliver Megolm keys to each device. Each sending device has an outbound Megolm session; it shares the session key with every device in the room over Olm-encrypted to-device messages, then encrypts messages with AES-256 in CBC mode, authenticates them with a truncated HMAC-SHA-256 and signs them with Ed25519. The decrypted payload carries the real event type, the content and the room ID, so a server can't move a message into another room. Clients must rotate their session after a configured time or message count, and whenever a member leaves.
That protects message content well. What it does not cover:
- Relationships. The spec requires
m.relates_toto be in the cleartext part of the event, because servers aggregate edits, threads and reactions. So servers see that Bob replied to a specific message, that Alice edited one, or which thread a message belongs to. - Reactions. An
m.reactionevent has no content besidesm.relates_to, and the reaction key (the emoji) lives there. The spec notes reactions "are normally not encrypted, as there would be no benefit in doing so". - Room state. Name, topic, avatar, membership, display names, power levels and join rules are plain state events.
- Read receipts and typing. These EDUs are sent to the room's servers without end-to-end encryption. Clients can send
m.read.privateinstead, which servers must not share with anyone else. - Devices. Servers that share a room with a user receive that user's device IDs and public keys, which they need to deliver keys to the right devices.
// A reply to an earlier message in an encrypted room.
// The body is in the ciphertext; the relationship is not.
{
"type": "m.room.encrypted",
"sender": "@bob:other.example",
"content": {
"algorithm": "m.megolm.v1.aes-sha2",
"session_id": "...",
"ciphertext": "...",
"m.relates_to": {
"m.in_reply_to": { "event_id": "$the_message_bob_replied_to" }
}
}
} Media works differently. In an encrypted room the client encrypts each file with a single-use AES-CTR key, uploads the ciphertext to its own homeserver, and puts the key, IV and a hash of the ciphertext inside the Megolm-encrypted message. Other servers only hold the file if one of their users downloads it, and even then they hold ciphertext. They can still see its size and when it was fetched. Room avatars and profile pictures are referenced from unencrypted state, so those images are not end-to-end encrypted.
For the cryptography behind this, and how Megolm compares with MLS, see libsignal vs MLS and why group chat security is different.
Encrypted State: MSC3414 and MSC4362
The Matrix community has been working on this gap. Two Matrix Spec Change proposals aim to encrypt room state:
- MSC3414 (Encrypted state events), opened in 2021. It is still marked work in progress, open, and needs implementation. It would also hide the event type from servers, which raises the hard question of how servers resolve state they can't identify.
- MSC4362 (Simplified encrypted state events), opened in September 2025. It is open and labelled as needing implementation. It encrypts every state event except the ones servers need:
m.room.create,m.room.member,m.room.join_rules,m.room.power_levels,m.room.third_party_invite,m.room.history_visibility,m.room.guest_accessandm.room.encryption. It is switched on with anencrypt_state_eventsflag in the room's encryption settings.
// MSC4362 (proposal, not in the spec): an encrypted room name
{
"type": "m.room.encrypted",
"state_key": "m.room.name:", // "<type>:<state_key>", still visible to servers
"content": { "algorithm": "m.megolm.v1.aes-sha2", "ciphertext": "..." }
} Under MSC4362 the event type and state key are packed into the visible state key so that state resolution keeps working. Servers would no longer read the room name, topic or avatar, but they would still see that the name was changed, by whom and when, and they would still see the full member list. Element described an experimental implementation across its Rust, WebAssembly and JavaScript SDKs and Element Web in September 2025, aimed at a labs flag. Neither proposal has been accepted into the specification as of November 2026, so in practice room state on Matrix is still unencrypted unless every client in the room opts in to an experimental feature.
What Happens When a Server Leaves
A user leaves through the make_leave and send_leave handshake, and the leave event is sent to the other servers. Once a server has no joined users left, other servers stop sending it new events, because events go to servers with joined members.
What the departing server does with its copy is up to its software and its admin. The spec does not require it to delete anything. Synapse, the most widely deployed homeserver, keeps rooms after local users leave unless an admin purges them or sets forgotten_room_retention_period, which purges rooms all local users have forgotten and is off by default. Everything that server received up to the leave, including the room's membership history and all encrypted messages, can stay on its disk indefinitely.
The same applies in reverse when the room's creator leaves: the room carries on as long as any server still has a joined user.
Retention, Erasure and Moderation Across Servers
Retention is set by many admins
A room with users on 30 homeservers has 30 independent copies, each under a different admin, retention policy, backup schedule and legal jurisdiction. Synapse supports message retention policies, but they are disabled by default and each server enforces its own. The matrix.org privacy notice puts it directly: your "username, display name, messages and files may be replicated across each participating homeserver", and federated homeservers "are subject to local laws and regulations".
Deletion is a request, not a guarantee
Deleting a message in Matrix sends a redaction event. Each server that receives it strips the original event down to a fixed list of keys (event ID, type, room ID, sender, state key, hashes, signatures, depth, prev_events, auth_events, origin timestamp, plus a few content keys for state events like membership). The content goes, but who sent something, where and when stays, because the graph and its signatures depend on it. Synapse also keeps the unredacted original in its database for seven days by default (redaction_retention_period). And a server that doesn't follow the rules can simply keep the original: as Matrix co-founder Matthew Hodgson wrote when Matrix prepared for GDPR in 2018, "there is no way to enforce a redaction over federation."
For GDPR erasure, matrix.org says it will forget its own copy of your data on request, and that other homeservers are expected to honour erasure requests, but "these homeservers are outside our span of control, so we cannot guarantee they will forget your data." Any organisation running Matrix with external federation inherits the same limit. For how this plays out in a regulated setting, see our Lupyd vs Matrix comparison.
Moderation is shared and per server
Room moderators act through state events: power levels, kicks, bans and m.room.server_acl, which can block whole servers from taking part in a room. Every participating server is expected to enforce these, and community tools subscribe to moderation policy lists (m.policy.rule.* state events) to ban users, rooms or servers across many rooms. Each server admin also moderates independently: they can block rooms on their own server or remove local users, but they can't change what other servers hold. In encrypted rooms, server admins and moderation bots that are not members of the room work from metadata and user reports, since only members' devices can read the content.
How Lupyd Differs
Lupyd makes a different trade-off, and it is fair to state it plainly. Lupyd is not federated: there is one relay, not many independent copies. Group channels use MLS (RFC 9420), and the server acts as an MLS external client. That server sees group membership, roles and group names, but not message content; direct messages use the Signal Protocol, and files and backups are encrypted on the device. So Lupyd avoids metadata being copied to many servers under many admins, but you depend on a single operator, and you cannot run your own server that talks to others the way Matrix allows. Lupyd has not had an external security audit yet, and its engine, Firefly, is open source.
If federation and self-hosting are what you need, Matrix is the established choice. If you want end-to-end encrypted team chat without replicated room metadata, try Lupyd.
Frequently Asked Questions
How does Matrix federation work?
Each user belongs to a homeserver. When users on different homeservers share a room, every one of those servers keeps a full copy of the room as a signed graph of events, sends new events to the others, and runs the same state resolution algorithm so they all agree on the room's current state.
Can other Matrix homeservers read my encrypted messages?
No. In an end-to-end encrypted room, message content is encrypted with Megolm and servers store only ciphertext. They can still see the sender, room, timestamp, device, reply and edit links, reactions, membership, and room state such as the name, topic and avatar.
Are Matrix room names encrypted?
Not in the current specification. Room names, topics and avatars are unencrypted state events stored by every participating server. MSC3414 and MSC4362 propose encrypting them, but both are still open proposals and implementations are experimental.
What happens to my messages when a homeserver leaves a room?
The server stops receiving new events once it has no joined users, but it keeps whatever it already received unless its admin purges the room. The specification does not require deletion.
Does deleting a Matrix message remove it from every server?
Deleting sends a redaction that well-behaved servers apply by stripping the content, while the sender, room, timestamp and graph links remain. Servers outside your control can't be forced to apply it, and Synapse keeps the original for seven days by default.
Final Takeaway
Matrix federation works by giving every participating homeserver a full, signed copy of the room. End-to-end encryption hides message content from all of them, but the room's structure stays readable: members, senders, timestamps, relationships, reactions and, for now, names, topics and avatars. That is the cost of letting independent servers authorise events without a central authority. If you run or join federated rooms, treat every participating server's admin as someone who can see that metadata and decides how long to keep it.
Sources & References
- Server-Server API, Matrix specification v1.19
- Client-Server API (end-to-end encryption, Megolm, encrypted attachments, relationships, receipts, server ACLs, moderation policy lists), Matrix specification v1.19
- Room Versions, Matrix specification
- Room Version 12 (event format, state resolution, redactions), Matrix specification
- MSC3414: Encrypted state events, matrix-spec-proposals
- MSC4362: Simplified Encrypted State Events, matrix-spec-proposals
- Hiding room metadata from servers, Element blog, September 2025
- Project Hydra: Improving state resolution, matrix.org blog, August 2025
- GDPR Compliance in Matrix, Matthew Hodgson, matrix.org blog, May 2018
- Privacy Notice, matrix.org (effective 25 August 2025)
- Synapse configuration manual (redaction_retention_period, forgotten_room_retention_period, retention)
Last updated: 4 November 2026.
- ✓ Every Matrix homeserver with at least one joined user keeps its own full copy of the room's signed event graph and current state; there is no single server that owns a room.
- ✓ When a new server joins, send_join gives it the room's full current state and auth chain, and it can fetch older history with /backfill and /get_missing_events.
- ✓ Megolm encrypts message content and the real event type, but every server still sees the sender, room, timestamp, device, reply and edit links, reactions, membership and room state such as name, topic and avatar.
- ✓ Encrypting room state is proposed in MSC3414 and MSC4362; both are open proposals as of November 2026, and MSC4362 would still leave membership, power levels and join rules visible.
- ✓ A server that leaves keeps what it received unless its admin purges it, redactions can't be enforced over federation, and erasure depends on every participating server's admin.
The workspace where nothing leaks
End-to-end encrypted channels and chat for your team, built on the Signal Protocol and MLS. Available on macOS, Windows, Linux and Android.