# m0serve > Realtime from a synchronous Python app, with no added infrastructure. > A WSGI/ASGI server written in Mojo: `pip install m0serve`, no Mojo > toolchain, no dependencies, nothing fetched at install. Its distinguishing > capability: held SSE and WebSocket connections gated by plain synchronous > views (Django, Flask, ...), with cross-worker publish. No Channels, no > Redis, no daphne, no second process. It is NOT the fastest server on raw > throughput; the benchmarks page says so with numbers. Platforms: macOS arm64 (13+), Linux x86_64 and aarch64 (glibc; the exact floor is in the wheel filename, and pip refuses rather than crashes on older systems). CPython 3.10-3.14; free-threaded builds run WSGI only. No Windows, no musl, no TLS/HTTP2 (terminate at a proxy). ## Operating contract - `m0serve MODULE[:ATTR]` serves WSGI or ASGI, detected from the object. Bare `MODULE` tries `MODULE.asgi:application`, `MODULE.wsgi:application`, `MODULE:app`, `MODULE.main:app`. - Zero-config topology follows the protocol: WSGI gets one loop with a pool of `min(cores, 8)` handler threads; ASGI gets an asyncio executor. `--workers N` preforks; `--mount PREFIX=SPEC` hosts several applications in one process, each in its own mode; `--threads N` is free-threaded CPython only and WSGI only (an ASGI app there is refused with exit 78). - Flags are strict and named in `m0serve --help`; flags override `M0_*` environment variables, which override defaults. - Refusals explain themselves on stderr and name the fix. Exit 2 is usage, 1 is a startup failure, 78 is a configuration the interpreter cannot run. `--version` prints `m0serve X.Y.Z`. - `m0serve --doctor [OPTIONS] [MODULE]` prints one JSON object and starts nothing: platform, interpreter, the application spec discovery chose and its protocol, the resolved topology, and a `checks` array where every failure carries `detail`, `fix` and `exit`. It exits with the code `m0serve` itself would exit with for the same arguments. - `--health-path /health` answers 200 in the server, before Python. - Realtime: a view's response with `M0-Hold: stream|websocket` and `M0-Channel: NAME` converts the connection to a held subscription; `from m0serve import m0pub; m0pub.publish(channel, data)` reaches every subscriber on every worker. Inbound WebSocket messages arrive as POSTs to `/ws/message` with `M0-Channel`/`M0-Slot`/`M0-Opcode` headers. `--realtime` composes with `--blocking-threads` and `--mount`; it is refused only where nothing could take a hold (an ASGI-only server). - `--static PREFIX=DIR` serves files from the server with sendfile, ETags and byte ranges; a miss falls through to the application. - SIGTERM drains in-flight work and exits 0; the server runs as PID 1 correctly. ## Start here - [Quickstart](https://m0serve.dev/quickstart.md): ten minutes to live multi-tab sync from one sync-Django file. Every fenced block is executed by CI. - [Running m0serve](https://m0serve.dev/docs/running.md): flags, modes, what to put in front of it, shutdown and exit codes. - [Capabilities](https://m0serve.dev/docs/spec.md): the capability matrix, one row per capability with the CI gate that proves it; `docs/spec.json` is the same data. `verified` means a gate runs on the stated cadence, not that the capability is correct; the page says so itself. - [Benchmarks](https://m0serve.dev/docs/benchmarks.md): every performance number, each rendered from a dated artifact and CI-checked against it, including where this server loses. - The realtime example the quickstart builds on, as a project: [apps/django_realtime](https://github.com/codetalcott/mojo-http/tree/main/apps/django_realtime/). ## Documentation Every page of https://m0serve.dev/ has a Markdown twin at the URL below; the same text rendered is at the URL without `.md` (with a trailing `/`). [llms-full.txt](https://m0serve.dev/llms-full.txt) is these pages in one file. - [m0serve: SSE and WebSockets from plain sync Django and Flask views](https://m0serve.dev/index.md): A WSGI/ASGI server written in Mojo. A synchronous view holds a Server-Sent Events stream or a WebSocket with two response headers and reaches every subscriber on every worker with one call. No Channels, no Redis, no daphne. pip install m0serve. - [Django Server-Sent Events and WebSockets without Channels: a ten-minute quickstart](https://m0serve.dev/quickstart.md): From pip install to live multi-tab sync from one sync Django file. Every command is executed by CI on every pull request. - [Running m0serve: flags, execution modes, proxies and shutdown](https://m0serve.dev/docs/running.md): How to start the server for the application you have: which mode runs by default and why, every flag and when to reach for it, what to put in front of it, how it drains, and what each exit code means. - [m0serve capability matrix: what the server does and the CI gate that proves it](https://m0serve.dev/docs/spec.md): One row per HTTP, WebSocket, WSGI, ASGI, security and deployment capability, each naming the test or CI step that proves it and the cadence it runs on. Also available as spec.json. - [m0serve documentation: every page, by what you are trying to do](https://m0serve.dev/docs.md): The map of the documentation grouped by intent: start here, understanding the design, measurements, the project record, and the Mojo framework underneath. - [Why m0serve has two execution modes: WSGI, ASGI, free-threading and the cliffs in each](https://m0serve.dev/docs/wsgi-vs-asgi.md): What each mode is, why one would not do, what WSGI gets from a handler pool and held connections, what ASGI gets from the asyncio executor, what free-threading changes, and where each mode has a cliff. - [PEP 3333 conformance of m0serve, clause by clause](https://m0serve.dev/docs/wsgi-conformance.md): Where the WSGI implementation stands against PEP 3333, clause by clause, and how the conformance is checked. - [m0serve roadmap: milestones, known issues, and what is not planned](https://m0serve.dev/docs/roadmap.md): The project's state on one page: the beta and 1.0 definitions, every known issue with what would retire it, what is deliberately not planned, and the index of design notes behind the decisions. ## Machine-readable - [spec.json](https://m0serve.dev/spec.json): the capability matrix as JSON, generated from SPEC.md - [sitemap.xml](https://m0serve.dev/sitemap.xml) - [Source repository](https://github.com/codetalcott/mojo-http) · [PyPI](https://pypi.org/project/m0serve/) ## Optional Measurements, the project record, the Mojo framework underneath, and the design notes: read when the question needs them, skip when context is short. - **Measurements** - [m0serve benchmarks: where it wins and loses against gunicorn, uvicorn and Granian](https://m0serve.dev/docs/benchmarks.md): How the benchmarks are run, what they compare, where m0serve loses and by how much, and the ways a benchmark of a server misleads. - [m0serve vs gunicorn, uvicorn and Granian: WSGI and ASGI throughput and latency](https://m0serve.dev/docs/wsgi-performance.md): Requests per second and tail latency for the WSGI and ASGI paths against gunicorn, uvicorn and Granian, rendered from dated benchmark artifacts. - [The Mojo HTTP server's own numbers, without Python in the path](https://m0serve.dev/docs/server-performance.md): Throughput and latency of the Mojo server serving Mojo handlers, with the environment each number was taken in. - [m0serve against real Django applications: the soak record](https://m0serve.dev/docs/real-app-validation.md): The server run against Django projects nobody here wrote, with what broke, what was measured and what changed as a result. A 1.0 requirement. - **The project** - [The mojo-http README: the whole repository, Mojo framework included](https://m0serve.dev/readme.md): The repository's own README: the realtime server, its install matrix and limits, and the Mojo web framework, Datastar adapter and SQLite bindings m0serve is one package of. - [m0serve changelog](https://m0serve.dev/changelog.md): Notable changes to mojo-http and the m0serve wheel, by version, in Keep a Changelog form. - [How an m0serve release happens](https://m0serve.dev/docs/releasing.md): The release path for the m0serve wheel: the gates CI runs, the two pre-release gates it structurally cannot, and the order. - [Where mojo-http came from: provenance and licensing](https://m0serve.dev/provenance.md): The repository's origin as an extraction from a private monorepo, the forked HTTP server inside it, and the licensing record. - **The Mojo framework** - [The m0-core C-ABI bundle: what ships and the licensing position](https://m0serve.dev/docs/ffi-distribution.md): What the shared library built from m0-core contains, how a foreign caller loads it, and the licensing position of the bundle. - [m0-sqlite performance findings: batched writes, mmap_size, json_each](https://m0serve.dev/docs/sqlite-performance.md): Measured findings for SQLite from Mojo: transactions around batch writes, mmap_size for large random reads, json_each for IN lists. - [SQLite virtual tables from Mojo: a feasibility record](https://m0serve.dev/docs/sqlite-vtab-feasibility.md): Whether SQLite virtual tables are reachable from Mojo through the m0-sqlite bindings, and what was tried. - **Design notes** - [A conformance-suite tier](https://m0serve.dev/notes/conformance-suite-tier.md): The server is pinned by hand-written probes against the RFC text -- smoke-ws speaks RFC 6455 from stdlib sockets, test_parsing.mojo covers the smuggling shapes directly -- and by no external suite on any cadence. A… - [The desktop-Mac server, and what the wheel gives up to ship](https://m0serve.dev/notes/desktop-mac-server.md): position may be as a desktop server — a Mac mini or Studio running an application on hardware someone already owns — rather than as a Linux container competing with Granian and uvicorn on rps. The reasoning is that… - [The Django server aims](https://m0serve.dev/notes/django-server-aims.md): Where the WSGI work is headed, and what gates each step — the full analysis with evidence is the design - [The drain does not read a request body in flight — resolved](https://m0serve.dev/notes/drain-and-request-bodies.md): Found by the soak driver's uploads population on color-separation (2026-09-02): with 9.7 MB multipart uploads in flight, every SIGTERM drain took exactly its 5 s budget, and the bisection put it on uploads alone.… - [v0.1.0: the first release](https://m0serve.dev/notes/first-release.md): backpressure and Last-Event-ID replay, auth, CORS, config, health, JSON-lines access logging, graceful shutdown, multi-worker fork - [Hold on a pool thread: the refusal that keeps --realtime off real applications](https://m0serve.dev/notes/hold-on-a-pool-thread.md): (REAL_APP_VALIDATION.md, 2026-08-26) showed the realtime mode is not deployable for the application class it exists for, and the reason is a refusal this server makes on purpose: --realtime refuses --blocking-threads,… - [Inbound WebSocket flow control — shipped 2026-08-31](https://m0serve.dev/notes/inbound-websocket-flow-control.md): The outbound direction was credit-gated (websocket.send awaits its window) and the inbound direction had no backpressure of any kind. Once the executor's submit channel filled, WSGIHandler.ws_message discarded each… - [The loop inversion — in progress 2026-08-28](https://m0serve.dev/notes/loop-inversion.md): The handoff's item 1: run the Mojo loop's pass as a callback inside the executor's run_forever, on one thread, so a request goes parse → app → response with no datagram and no cross-thread wake. At c16 the pump batches… - [The Mojo handler pool — shipped 2026-08-28](https://m0serve.dev/notes/mojo-handler-pool.md): The offload pool, for handlers written in Mojo. Planned as a kill-criterion spike: if a slow Mojo handler did not strand the connections behind it the way a slow Python view does, the branch was to be deleted and this… - [Mojo language capabilities, surveyed 2026-08-28](https://m0serve.dev/notes/mojo-language-capabilities.md): A pass over what the tree uses of the language, prompted by "are we fully tapping Mojo?". The short answer is yes wherever it was measured to pay — SoA span-based headers (+72%, the largest single win here), SIMD… - [The outbox sweep — taken, scoped (2026-08-29)](https://m0serve.dev/notes/outbox-sweep.md): The second lever the inversion entry named. Every pass swept all 1,024 slots for a streaming one to drain, and the miss path — two flag loads per slot, none set — measured 1.2–1.3 µs per pass in isolation, on a pass… - [Proven once, unloaded: an inventory of the gates with that shape](https://m0serve.dev/notes/proven-once-unloaded.md): The v0.15.1 bug came from a shape rather than an oversight: proven once, by a smoke, and stressed not at all. The measure of how little that guarantees is exact. Reverting the websocket.send credit gate was caught on… - [Pacing the pump's loop thread](https://m0serve.dev/notes/pump-pacing.md): What the sweep measurement exposed: at c16 the pump's throughput depends on how long its loop thread spends per pass, in a way that an accidental 1.2 µs improved by 3%. An explicit pause before flushing a partial batch… - [A request body still arriving at SIGTERM held the drain to its deadline — resolved](https://m0serve.dev/notes/request-body-at-sigterm.md): The drain loop read nothing new, so a half-received upload was neither completed nor closed until the 5 s budget expired: the client was reset and the process exited at 5.09 s. Found by the soak driver's uploads… - [Considered, not built: routes that carry a function](https://m0serve.dev/notes/routes-that-carry-a-function.md): Router.match returns an Int and the caller dispatches on it, which is why three of five Mojo apps skip the router and hand-write if path == …. Route-to-function is reachable on Mojo 1.0 — verified by spike, not assumed.… - [Streamed WSGI bodies — shipped 2026-08-27](https://m0serve.dev/notes/streamed-wsgi-bodies.md): The last buffered shape. A generator the application did not size — Django's StreamingHttpResponse, the thing every Django SSE tutorial returns — was joined whole by the shim, so a never-ending one never answered and… - [Hardening the streaming seam — shipped 2026-08-27](https://m0serve.dev/notes/streaming-seam-hardening.md): 0.14.0 shipped a fix for a hang that had been live all day: the shim keyed a connection slot's executor state — credit window, event, disconnect mark — by SLOT, while the loop recycles a slot the instant it closes a… - [Structured CI results](https://m0serve.dev/notes/structured-ci-results.md): to $M0_RESULTS as one JSON line; the smoke job renders them into the run summary with a headroom column and uploads ci-results-. Five sites are instrumented: the WSGI and ASGI RSS guards, the pool's fast-request… - [Traceability: stable ids, then declared coverage](https://m0serve.dev/notes/traceability.md): SPEC.md is a requirements traceability matrix -- the standard artifact in safety-critical software, whose defining property is that it traces BOTH ways: every capability to its evidence, and every piece of evidence back… - [The WebSocket close path RSTing instead of FINning — resolved v0.15.1](https://m0serve.dev/notes/websocket-close-rst.md): Listed as a suspected race for two sightings, both on macOS CI and never locally. It was not a flake: RFC 6455 §5.5.1 requires the endpoint that sends Close to WAIT to receive one, and the loop closed as soon as its own… - [The WebSocket send window — shipped 2026-08-28](https://m0serve.dev/notes/websocket-send-window.md): The one path above that an ordinary application reaches, made to work rather than merely to fail honestly. websocket.send had no window: an app faster than its client filled the loop's 64 KB per-slot outbox and every… - [WSGI vs ASGI: the design record](https://m0serve.dev/notes/wsgi-vs-asgi-history.md): The obvious roadmap item after a conformance-tested WSGI host is an ASGI host. This document is the case for not building one now. The short form: ASGI answers two independent questions — how does Python code overlap…