A homeserver with no server
Matrix has become the standard for decentralized, end-to-end encrypted communication, but the cost of running a homeserver has always been infrastructure-heavy. A typical Synapse deployment requires PostgreSQL for persistence, Redis for caching, a reverse proxy, TLS certificate management, and ongoing capacity planning — whether the server sees one message a day or a million.
A proof-of-concept project set out to remove that operational overhead entirely by porting a Matrix homeserver to Cloudflare Workers. The result is a serverless architecture where deployment is a single command, costs scale to near zero when idle, and every connection uses post-quantum TLS by default. The source is available on GitHub for anyone to deploy their own instance.
Mapping the stateful stack to serverless primitives
The port began with Synapse, the Python reference homeserver, and required rethinking every storage assumption. Traditional homeservers rely on a central SQL database with strong consistency for event authorization, room state resolution, and cryptographic verification. The logic was rewritten in TypeScript on the Hono framework, with each stateful component mapped to a Cloudflare primitive: D1 for PostgreSQL, KV for Redis, R2 for the filesystem, and Durable Objects for real-time coordination.

The key insight was that different data types need different consistency guarantees, so each primitive is used for what it does best.
D1 for the relational data model
D1 stores everything that must survive restarts and support queries: users, rooms, events, and device keys, across more than 25 tables. Because D1 is SQLite-based, existing queries ported with minimal changes — joins, indexes, and aggregations work as expected.
One hard lesson emerged: D1's eventual consistency breaks foreign key constraints. A write to rooms might not be visible when a subsequent write to events checks the constraint. All foreign keys were removed, and referential integrity is now enforced in application code.
KV, R2, and Durable Objects for everything else
Ephemeral state — OAuth authorization codes that live for ten minutes, refresh tokens that last a session — maps to KV, whose global distribution keeps auth flows fast regardless of user location. Matrix media maps directly to R2, producing content-addressed URLs with free egress.
For operations that cannot tolerate eventual consistency, Durable Objects provide single-threaded, strongly consistent storage. When a client claims a one-time encryption key, that key must be atomically removed; if two clients claim the same key, encrypted session establishment fails. UserKeysObject handles E2EE key management, RoomObject manages real-time events like typing indicators and read receipts, and UserSyncObject maintains to-device message queues. All other data flows through D1.
#[durable_object]
pub struct UserKeysObject {
state: State,
env: Env,
}
impl UserKeysObject {
async fn claim_otk(&self, algorithm: &str) -> Result<Option<Key>> {
// Atomic within single DO - no race conditions possible
let mut keys: Vec<Key> = self.state.storage()
.get("one_time_keys")
.await
.ok()
.flatten()
.unwrap_or_default();
if let Some(idx) = keys.iter().position(|k| k.algorithm == algorithm) {
let key = keys.remove(idx);
self.state.storage().put("one_time_keys", &keys).await?;
return Ok(Some(key));
}
Ok(None)
}
}
Built-in post-quantum protection
Cloudflare has deployed post-quantum hybrid key agreement across all TLS 1.3 connections since October 2022, so every connection to the Worker automatically negotiates X25519MLKEM768 — a hybrid that combines classical X25519 with ML-KEM, the NIST-standardized post-quantum algorithm. Classical cryptography relies on problems that quantum computers running Shor's algorithm could solve trivially; ML-KEM is based on lattice problems that remain hard even for quantum machines. Because the two algorithms are combined, both must fail for a connection to be compromised.
Achieving the same on a traditional deployment would require upgrading OpenSSL or BoringSSL, configuring cipher suite preferences, testing every Matrix client, and handling PQC negotiation failures gracefully. On Workers, Chrome, Firefox, and Edge all support X25519MLKEM768, and the security posture improves automatically as Cloudflare's PQC deployment expands.
Tracing an encrypted message
Understanding where encryption happens matters. When a message is sent through the homeserver, the sender's client encrypts the plaintext with Megolm — Matrix's end-to-end encryption. That encrypted payload is then wrapped in another layer of TLS for transport, using X25519MLKEM768.

The Worker terminates TLS, but what it receives is still Megolm ciphertext. That ciphertext is stored in D1, indexed by room and timestamp, and delivered to recipients. No party in the infrastructure chain — Cloudflare, the Worker, or the homeserver operator — ever sees the plaintext. The message exists only on the sender's and recipient's devices. This produces two independent layers of protection: the transport layer (TLS) protects data in transit with post-quantum cryptography, while the application layer (Megolm E2EE) protects message content with classical Curve25519, encrypted before it ever hits the network.
Homeserver operators can still see metadata — which rooms exist, who is in them, when messages were sent — but never message content. Media in encrypted rooms is encrypted client-side before upload, and private keys never leave user devices.
Full E2EE and OAuth support
The implementation supports the complete Matrix E2EE stack: device keys, cross-signing keys, one-time keys, fallback keys, key backup, and dehydrated devices. Modern Matrix clients use OAuth 2.0/OIDC instead of legacy password flows, so a full OAuth provider is included, with dynamic client registration, PKCE authorization, RS256-signed JWT tokens, token refresh with rotation, and standard OIDC discovery endpoints. Pointing Element or any Matrix client at the domain discovers everything automatically.
curl https://matrix.example.com/.well-known/openid-configuration
{
"issuer": "https://matrix.example.com",
"authorization_endpoint": "https://matrix.example.com/oauth/authorize",
"token_endpoint": "https://matrix.example.com/oauth/token",
"jwks_uri": "https://matrix.example.com/.well-known/jwks.json"
}
Sliding Sync for mobile
Traditional Matrix sync transfers megabytes on initial connection, draining mobile battery and data plans. Sliding Sync lets clients request only what they need — the 20 most recent rooms with minimal state, then additional ranges as users scroll. The server tracks position and sends only deltas. Combined with edge execution, mobile clients can connect and render their room list in under 500ms even on slow networks.
Comparing the operational cost
The traditional architecture demands ongoing attention that a serverless model eliminates. For a homeserver serving a small team, the difference is measurable.
Traditional (VPS) | Workers | |
|---|---|---|
Monthly cost (idle) | $20-50 | <$1 |
Monthly cost (active) | $20-50 | $3-10 |
Global latency | 100-300ms | 20-50ms |
Time to deploy | Hours | Seconds |
Maintenance | Weekly | None |
DDoS protection | Additional cost | Included |
Post-quantum TLS | Complex setup | Automatic |
Workers also removes the need for capacity planning and over-provisioning — scaling is automatic, and the economics improve further at higher usage. Deployment becomes wrangler deploy; TLS, load balancing, DDoS protection, and global distribution are handled by the platform. A traditional homeserver in us-east-1 adds hundreds of milliseconds of latency for users in Asia or Europe, while Workers execute in over 300 locations worldwide.
This project demonstrates that complex, stateful protocols can run on serverless primitives without sacrificing functionality. By mapping Postgres to D1, Redis to KV, and mutexes to Durable Objects, the operational layer disappears — leaving only the application logic and the data itself.



