The ready-made package for running your own server for Sezi — end-to-end encrypted, outward-closed group messaging and application platform. The server is a blind relay: it cannot read message content, media or plugin data. It queues and moves encrypted bytes, nothing more. The keys stay on the devices.
- Create a Cloudflare account (free): https://dash.cloudflare.com/sign-up
- Press Deploy to Cloudflare above. Cloudflare copies this repository into your own GitHub account, creates the database (D1) and the rate-limit store (KV, the brute-force brake) for you, and deploys.
- When the deploy finishes you get an address:
https://sezi-server.<your-account>.workers.dev - In the Sezi app, under "Add a server → Set up your own", paste that address. The first account to register becomes the server's owner — invitations, limits, everything, managed from the app.
That is all. The signing keys are generated by the server on its first boot; the database schema installs itself on the first request. No wrangler secret, no migration command. No card details.
Lite install: this template installs "light" — messaging, groups and calls work in full; for photo and file sharing you enable R2 storage in Cloudflare afterwards (it asks for card verification; free up to 10 GB, a small fee beyond). How: enable R2 in the dashboard → create a bucket named
sezi-media→ Worker → Settings → Bindings → add R2 (MEDIA/sezi-media). No code, no commands; the app shows the feature by itself. Running it on your own machine withwrangler dev? Then no R2 subscription is needed at all: uncomment the[[r2_buckets]]block inwrangler.tomland media is stored on the local disk.
- 🔒 A blind relay — content is E2E encrypted; the server sees only sizes and counts
- 👑 Owner administration from the app — invitations, members, retention, storage limits, the usage panel
- 📊 Usage panel — storage / calls (TURN) / daily and monthly volume; optionally a "matches your bill exactly" mode with a CF API token (which you also enter from the app)
- 💸 Budget guards — a monthly TURN ceiling plus owner-set storage quotas: no surprise invoice on the free tier
- 📱 Multi-device, groups (Megolm), voice and video calls (LAN/STUN; CF TURN if you want it), a plugin platform
- 🧲 Pluggable storage — keep media in YOUR OWN store instead of, or alongside, R2 (see below)
The media store is not limited to R2. You can attach any S3-compatible store to the server from the app — no code, no commands, no redeploy:
- Cloud: Backblaze B2, Wasabi, iDrive e2, MinIO in the cloud, another R2 account…
- Your own disk (self-host): a MinIO running on the same machine
(e.g. a Raspberry Pi with a big HDD) →
http://localhost:9000
How: in the app, as owner → Server → panel → the STORAGE section → add a store (endpoint / region / bucket / access keys) → Test. That is it; new media is written there from then on. The same place drains a store (blobs are moved off it) and removes it; health and fullness are shown there too.
A note on privacy: media blobs are written end-to-end encrypted — whichever provider you attach (Backblaze, MinIO, anyone) sees only encrypted rubbish. The keys never leave the devices.
git clone https://github.com/hasantr/sezi-server && cd sezi-server
npx wrangler login
npx wrangler d1 create sezi # put the database_id from the output into wrangler.toml
npx wrangler kv namespace create RATE_LIMIT # put the id from the output into wrangler.toml
npx wrangler deployThe build/ directory contains compiled WASM — you do not need to install Rust. To build it
yourself from source: cargo install worker-build && worker-build --release.
You can run the server around the clock on your own VPS or device (Raspberry Pi / mini-PC) without touching Cloudflare at all. This keeps wrangler dev's local workerd alive under systemd and exposes it either through a cloudflared tunnel or a caddy TLS reverse proxy.
⚠️ Security-critical: in a self-managed install the relay must listen on127.0.0.1only; outside access must come solely through the tunnel or the TLS proxy.--ip 0.0.0.0puts the server on the internet as plain HTTP — do not.
A ready hardening kit (an EXACT wrangler pin, localhost binding, a WS shim, atomic backup, a healthz watchdog, ufw) is walked through step by step in deploy/ → deploy/README.md. Self-hosting uses its own config (wrangler.selfhost.toml, with the rate-limit KV enabled); the "Deploy to Cloudflare" button is unaffected by it.
| What | How | What happens without it |
|---|---|---|
| Your own signing key | wrangler secret put JWT_SIGNING_KEY (Ed25519 PKCS8 PEM) |
Generated automatically on first boot (stored in D1) |
| TURN relay for voice and video | CF Realtime → TURN key → TURN_KEY_ID + TURN_API_TOKEN secrets |
Calls work over LAN/STUN (enough on most networks) |
| Android push | A Firebase project → FCM_PROJECT_ID var + FCM_SERVICE_ACCOUNT secret |
Delivered while the app is open (WS); it waits while closed |
| Usage report matching the bill | From the app: Server → Usage → CF Analytics → enter a token | The server reports its own counters |
- On your fork in GitHub, use Sync fork → Update branch.
- On a Cloudflare-connected install this usually triggers a redeploy on its own. If it does not, trigger it by hand from the Cloudflare dashboard → Workers & Pages → your server → the Deployments/Builds tab → "Retry/Deploy"; or with one command on your computer, inside the fork's directory:
npx wrangler deploy.
The database schema updates itself (self-migration) — no data is lost.
Watch out for a database name collision. When you install a second server into the same Cloudflare account, make the D1 database name unique on the setup screen (e.g. sezi-family, sezi-school). If you reuse the same name the two servers share one database and their members and data mix together. Different Worker and KV names are harmless. Note that free-plan quotas (requests, storage) are shared across every server in the account.
Deleting the Worker does not delete the database. D1 is an account-level resource that the Worker merely binds, so deploying again under the same name hands the new Worker the old database — the previous owner, members and signing key all come back. A clean start needs the database gone too. In full, what a deploy creates:
| Name | What it is |
|---|---|
sezi-server |
The Worker. Deleting it stops everything and takes the user inboxes with it. |
sezi |
The D1 database. The one people forget. |
RATE_LIMIT |
The KV namespace. Harmless to leave. |
sezi-media |
The R2 bucket, only if you enabled media. Cloudflare will not delete it until it is empty. |
The GitHub repository the deploy created in your account stays until you remove it there.
Rust → WASM Cloudflare Worker. D1 (SQLite) for metadata and the queue, R2 for encrypted media, a per-user Durable Object inbox (WebSocket). Content is end-to-end encrypted with vodozemac (Olm/Megolm) — the server cannot read it, by design.
Not decided yet — to be added before release.