Alpha ZealPHP is early-stage and under active development. APIs may change between minor versions until v1.0. Feedback and bug reports welcome on GitHub.

Why ZealPHP?

PHP powers ~71% of the web (per W3Techs) — always as a worker behind a C-based HTTP server.
ZealPHP runs PHP as the HTTP server itself: coroutine-native, always-on, WebSocket/SSE/timers first-class.

Live production proof: Selfmade Ninja Labs runs the same PHP/MongoDB codebase on Apache and ZealPHP in production. Two servers, one volume, zero downtime during migration. Read the migration case study →

The problem

In the traditional model (PHP-FPM, mod_php), the HTTP server is C — nginx or Apache. PHP is the worker that bridges in via FastCGI and exits per-request. Every request starts from scratch — no shared state, no persistent connections, no coroutines. Building a WebSocket server, streaming AI responses, or running background tasks requires leaving PHP entirely for Node.js, Go, or Python. ZealPHP collapses this: the HTTP server IS PHP, long-lived, with a coroutine per request.

Existing async PHP solutions are either too low-level (raw Swoole, ReactPHP, AMPHP), framework-locked (Laravel Octane), or not native PHP (FrankenPHP, RoadRunner). None provide a full-stack, coroutine-native framework with a migration path for existing PHP apps.

ZealPHP's approach

Coroutine-native, not event-loop

Write synchronous-looking code. Under the hood, every I/O call (file, curl, PDO, sleep) yields the event loop via OpenSwoole's coroutine hooks. Thousands of concurrent requests per worker, zero callback hell.

Full-stack framework, not a library

Routing, PSR-15 middleware, templating with streaming, WebSocket, SSE, shared memory, task workers, sessions — all integrated. Write $app->route() and ship. Not 12 packages wired together.

Legacy PHP bridge — LAMP-style file routing

Drop .php files in public/ and they route automatically — just like Apache. session_start(), header(), $_GET, echo all work unchanged via ext-zealphp. Drop files in api/ and they become REST endpoints. Many traditional PHP patterns — including WordPress — run through the CGI worker bridge in compatibility mode, with documented limits. Migrate at your own pace — file by file, feature by feature.

PHP as the application server

HTTP, WebSocket, task workers, timers, shared memory, sessions — in one PHP application server, started with php app.php. On one node you can skip the Redis/Supervisor/cron tier; for cross-node deploys, ZealPHP's Store + pub/sub flip to a Redis/Valkey backend with one line of config. Front it with nginx/Caddy/Traefik in production for TLS + horizontal scaling.

Competitive landscape

Every project below serves a different need. This comparison is about where ZealPHP fits — not about which is "best."

Project Model Routing WebSocket Streaming Shared Memory Legacy PHP
ZealPHP Coroutine Built-in Built-in yield / SSE / stream() Store + Counter CGI worker
ReactPHP Event loop Manual Via packages Manual No No
AMPHP Fiber Manual Via packages Manual No No
FrankenPHP Go worker Via framework Via framework Via framework No Partial
RoadRunner Go worker Via framework Go plugin Via framework No No
Laravel Octane Swoole/RR Laravel Via packages Limited Limited No
Raw Swoole Coroutine Manual Manual Manual Table / Atomic No

ZealPHP vs raw OpenSwoole — engine vs harness

The most common HN-shaped question: “it’s just OpenSwoole — why the extra layer?” Same shape as “it’s just Node http — why Express?” You can write raw onRequest handlers and ship them; people do. The catch is that every project that does ends up re-inventing the same 12 things. OpenSwoole is the engine. ZealPHP is the harness that lets you steer it without re-implementing the glue.

OpenSwoole gives you

the runtime — raw power

  • HTTP server + WebSocket\Server primitives (onRequest, onMessage, onOpen, onClose)
  • Coroutines: go(), Channel, WaitGroup, Coroutine::getContext()
  • Atomic + Table — lock-free shared memory across workers
  • Coroutine\Http\Client + DNS + sleep + file I/O hooks
  • OpenSwoole\Runtime::enableCoroutine(HOOK_ALL) — PHP I/O yields the reactor automatically
  • Process\Pool + master/manager/worker lifecycle
  • Timers: Timer::tick(), Timer::after()
  • Process for sub-process fork + IPC pipes
  • FastCGI coroutine client (v22.1+)

ZealPHP adds on top

the harness — usable surface

  • Routingroute() + nsRoute + nsPathRoute + patternRoute with reflection-based parameter injection (/routing)
  • PSR-15 middleware stack — 18 built-ins (CORS, ETag, Range, Compression, RateLimit, BasicAuth, HostRouter, ScopedMiddleware, …) covering common Apache mod_* / nginx behaviors (/middleware)
  • ext-zealphp overridessession_start(), header(), setcookie(), http_response_code(), headers_list(), the entire session_*() family, flush(), apache_request_headers(), is_uploaded_file() all just work, routing to per-request state instead of mutating process globals
  • Coroutine-safe sessionsCoSessionManager with per-coroutine isolation, no $_SESSION races across concurrent requests (/sessions)
  • TemplatingApp::render / renderToString / renderStream / include / fragment — htmx-style named regions, streaming-Generator output, sub-template composition (/templates)
  • Universal return contractint = status, array = JSON, Generator = SSE/SSR stream, string = HTML, Closure = param-injected stream — one contract across route handler, public file, API closure, fallback, error handler, render(), include() (/responses#return-contract)
  • ZealAPI — file-based REST: drop api/device/list.php/api/device/list auto-routes; auth hooks (authChecker, adminChecker, usernameProvider) (/api)
  • CGI worker bridgecgiMode('pool' | 'proc' | 'fork' | 'fcgi') dispatches legacy public/*.php files with true global-scope isolation ('pool' is the default: a pre-spawned warm worker pool that keeps the interpreter in memory, ~1–3 ms; 'proc' spawns on demand via proc_open, ~30–50 ms; 'fork' is the experimental Apache MPM prefork runner that forks a fresh child per request at ~1 ms, requires pcntl+posix; 'fcgi' forwards to an external FastCGI / php-fpm pool) (/legacy-apps)
  • Pluggable Store + Counter backends — one API, three backends: Table (single-node, nanoseconds), Redis/Valkey (cross-node + persistence, ~ms), Tiered (L1 Table + L2 Redis with HMAC-signed cross-node L1 invalidation) (/store)
  • Cross-host messagingStore::publish / App::subscribe for fire-and-forget pub/sub, publishReliable / subscribeReliable for Streams-backed at-least-once delivery via consumer groups, WSRouter for cross-server WebSocket routing, first-class WS rooms with federated membership (/pubsub)
  • Stream wrapper for php://input — legacy file_get_contents('php://input') in JSON APIs just works in long-running workers
  • CLI toolingphp app.php start/stop/restart/status/logs + daemonization + per-port PID files + log filters (--access, --debug, --server, --zlog)
  • Coroutine-safe superglobals & lifecycle presetsApp::mode(App::MODE_COROUTINE_LEGACY) runs legacy request-style PHP concurrently: the seven $_* superglobals (S1), $GLOBALS (S2), function-local statics (S5a), and require_once state (S7) all isolate per coroutine via ext-zealphp; per-request define() isolation (S10) is available as a separate opt-in via App::defineIsolation(true). One-call lifecycle presets: App::mode(App::MODE_COROUTINE) (modern default), App::mode(App::MODE_LEGACY_CGI), App::mode(App::MODE_MIXED). (/coroutines#lifecycle-modes)

When raw OpenSwoole is the right choice: you’re building a custom binary-protocol server (your own message broker, database driver, ASR pipeline), you can’t install custom PHP extensions (locked-down shared host), or you’re explicitly building another framework. For everything else — HTTP, WebSocket, SSE, REST APIs, web apps with sessions, AI-streaming endpoints — the harness saves you weeks per project and keeps the migration door open to existing PHP code.

ZealPHP vs Laravel Octane

Two different problems:

  • Laravel Octane accelerates an existing Laravel application by serving it from a long-running Swoole / RoadRunner / FrankenPHP worker. If you're on Laravel and want it faster, use Octane.
  • ZealPHP is a framework-agnostic layer over OpenSwoole. Routing, middleware, WebSocket, SSE, shared memory, timers, and the legacy PHP bridge are exposed as first-class primitives — no Laravel kernel in between.

If you have a Laravel app, Octane is the right tool. If you're starting fresh, migrating non-Laravel PHP, or need lower-level coroutine primitives, ZealPHP is built for that.

The migration ladder

You don't have to learn a framework to start. Drop files in a folder. Upgrade when you need to.

0. setFallback() — your entire existing app runs unchanged on OpenSwoole
1. public/*.php — LAMP-style file routing. $_GET, session_start(), echo just work
2. api/*.php — drop a file, get a REST endpoint. ZealAPI auto-routes by filename
3. $app->route() — WebSocket, SSE, streaming when you're ready
4. superglobals(false) — full coroutine mode, thousands of concurrent requests

When to use ZealPHP

Good fit

  • AI/LLM apps with streaming responses
  • Real-time dashboards and live updates
  • WebSocket apps (chat, collaboration)
  • High-concurrency APIs — workloads where the I/O-concurrency-per-worker model pays off (measure before you commit)
  • Migrating large PHP codebases to async
  • LAMP-style PHP devs who want async without learning a framework
  • Single-process deployments (no infra complexity)

Not the right fit

  • Already invested in Laravel ecosystem
  • Need shared hosting (requires CLI access)
  • Building a custom protocol server
  • Committed to AMPHP / Revolt / ReactPHP — Fiber libraries that drive Revolt's event loop, a separate scheduler from OpenSwoole's reactor (an app picks one)
  • Need byte-for-byte Apache/nginx config replacement — ZealPHP covers the common .htaccess/nginx.conf patterns (rewrite, headers, auth, rate limit, MIME, etc.) but is not a drop-in replacement for every directive

Note: OpenSwoole 26.2 uses PHP's zend_fiber API as its coroutine-context backend (better Xdebug interop) — but Fiber-driven libraries like AMPHP / Revolt still run on a separate scheduler from Swoole's reactor.

Benchmarks

Headline numbers: 117k req/s text, 106k JSON, 50k templated with 4 HTTP workers under the full PSR-15 middleware stack — 0 failures across 150k requests. Numbers are from one benchmark setup; real-world performance depends on payload size, I/O, OS limits, and tuning. Reproduce in 60s with scripts/bench_vs_express.sh. Full methodology, latency percentiles, caveats: /performance.

Method  |  4 workers, full middleware (CORS + ETag + sessions + PSR-7 routing), ab -n 50000 -c 200 -k  |  PERF.md  |  reproduce locally

Ready to try it?

From zero to running server in 60 seconds.

Get started →