webserver README

The project's README from github.com/Qbix/webserver. As one markdown file: README.md.

⚡ Qbix Server

Run your existing PHP codebase 10–100× faster than nginx + php-fpm

A pure PHP web server. No nginx, no Apache, no php-fpm. One process serves static files, PHP scripts, WebSocket connections, and a live dashboard.

The problem with php-fpm

Whether opcache is enabled or not, the vast majority of production PHP code is I/O-bound. Workers wait for the database, the filesystem, an API call, a cache server. During that wait, the worker is doing nothing — but it's still holding 30–60MB of RAM. That's the bottleneck. On a 4GB server, php-fpm gets maybe 80 workers. Each one blocks on a 200ms query, so you get ~400 req/s. That's the ceiling.

The problem with Swoole, RoadRunner, and FrankenPHP

They try to solve this by making PHP evented, like Node.js. Swoole's coroutines can multiplex I/O within a single worker — but only if you rewrite your code to use Swoole\Coroutine\MySQL, Swoole\Coroutine\Http\Client, and so on. Every PDO::query(), every file_get_contents(), every curl_exec() in every WordPress plugin, Laravel package, and Drupal module uses blocking I/O. It doesn't yield. Swoole can't help with code that doesn't cooperate. RoadRunner and FrankenPHP don't even try coroutines — they use the same worker-count-limited model as fpm.

How Qbix solves it

Instead of making each worker do more, Qbix runs more workers. The server loads your entire framework into a parent process, then calls pcntl_fork(). The kernel marks every page copy-on-write. Each worker shares the parent's loaded classes and only pays for pages it actually writes to during the request. A WordPress-like request dirties 30 pages = 120KB. So the same 4GB that gives fpm 80 workers gives Qbix thousands.

Your code runs unmodified, in two modes:

Persistent workers (default) — workers stay alive across requests. Between each request, a Reflection-based snapshot restores all static properties in 0.03ms. 28 PHP functions (header(), session_start(), ini_set(), set_error_handler(), etc.) are shimmed via source transformation so they reset correctly. This is how you get 2,294 req/s on CPU-bound work and 1,060 req/s under I/O.

Fork-per-request — if persistent mode doesn't work for your code (functions with internal static variables, plugins that register global state in ways the shim can't track), set forkPerRequest: true. Each request gets a fresh fork. It's slower than persistent mode, but each forked worker still costs only 120KB instead of 50MB, so you can run 100× more of them than fpm on the same hardware. That's the whole point — blocking I/O doesn't matter when you have enough workers, and COW makes "enough workers" nearly free.

What it replaces

nginx + php-fpm Qbix Server
💾 Memory per worker 30–60MB (duplicated) ~200KB (COW, measured)
👥 Concurrent PHP (1GB) ~24 workers ~5,000 (typical)
🔒 Isolation Statics leak between requests Snapshot reset — no leaks
🚀 Throughput (CPU-bound) ~400 req/s (Swoole 4w) 2,294 req/s (100w)
🚀 Throughput (I/O, same RAM) 78 req/s (fpm/Swoole 4w) 1,060 req/s (100w)
🌐 WebSocket Needs a separate server Built in
🧩 Cache invalidation Whole-page only X-Cache-Tree — per-component
⚙️ Setup nginx + fpm pools + sockets php qbixserver.php

See BENCHMARKS.md for full methodology and reset.md for what gets restored between requests.

What a "real-time PHP app" used to require

nginx for reverse proxy and static files. php-fpm to run PHP. Node.js for a Socket.IO server. Redis for pub/sub between fpm and Node. supervisor to keep it all running. Docker to make it deployable. Six processes, three languages, two runtimes.

Qbix Server replaces all six with one process. HTTP, WebSocket (with Socket.IO protocol), SSE, sessions, uploads, static files, .htaccess — same port, same file. No Redis, no Node, no pub/sub glue. Download a 4.5MB binary, run it, done. Pure PHP.

You can also package your entire app — code, assets, SQLite database — into that binary and distribute it as a single file. Double-click on Windows, ./myapp --open on Mac or Linux, the browser opens and the app is there. No PHP to install, no web server to configure, no database to set up. 5 MB, not 200 — because we open the browser that's already there instead of shipping Chromium like Electron does. How it works →


Documentation

Topic What it covers
🏎️ Why Not php-fpm? COW memory model, comparison with Swoole and FrankenPHP
🔒 Server Headers Cache-Control, X-Cache-Tree, X-Accel-Redirect, ETag
🌐 HTTP Fork-per-request mode, request lifecycle
🔌 WebSocket & Rooms Process per connection, rooms, Socket.IO, SSE, chat example
🛤️ Routing Clean URLs, .htaccess, DirectoryIndex
📂 PHP Framework The micro-framework: handlers, events, Q classes
⚙️ Configuration JSON config, CLI options, presets
📦 Running & Building Source, phar, binary. Building static binaries. Requirements
📀 Binaries & Signing Pack apps, manage like zip, ECDSA M-of-N signing, Rekor, platform signing
🏗️ Architecture Persistent workers, COW, execution model, mental model, benchmarks
📊 Dashboard & Panel Live stats, control panel tabs
🚀 Deploy & Federation Rsync deploy, cluster replication, inter-server trust
🔍 API Discovery OpenAPI, MCP, qbix.json, HTTP/2
🧩 Compatibility SAPI emulation, 28 shimmed functions, class ownership, tests
📈 Benchmarks Full methodology and numbers
🔄 State Reset What gets restored between requests
🔀 Migrate from nginx Server blocks, try_files, proxy_pass, gzip
🔀 Migrate from Apache .htaccess unchanged, VirtualHost mapping
🔀 Migrate from Caddy Automatic HTTPS, on-demand TLS → autohost
✅ Test Results 140 end-to-end tests
🗺️ Roadmap What's next
📄 License MIT

Quick Start

git clone https://github.com/Qbix/webserver
cd webserver
php qbixserver.php

Or grab a self-contained binary (PHP bundled, nothing to install):

# Linux
curl -LO https://github.com/Qbix/webserver/releases/latest/download/qbixserver-linux-x86_64
chmod +x qbixserver-linux-x86_64
./qbixserver-linux-x86_64

# macOS (Apple Silicon)
curl -LO https://github.com/Qbix/webserver/releases/latest/download/qbixserver-macos-arm64
chmod +x qbixserver-macos-arm64
./qbixserver-macos-arm64

# Windows
curl -LO https://github.com/Qbix/webserver/releases/latest/download/qbixserver-windows-x64.exe
qbixserver-windows-x64.exe

Use With Your Existing Codebase

If you already have a PHP app running on nginx + php-fpm, switching is one command. The server reads your .htaccess, rewrites URLs to your front controller, and runs your code with 28 functions shimmed so static variables, sessions, and headers work correctly between requests.

Laravel:

cd my-laravel-app
php /path/to/qbixserver.php --root=public --preset=laravel --port=8080

Symfony:

cd my-symfony-app
php /path/to/qbixserver.php --root=public --preset=symfony --port=8080

WordPress:

cd my-wordpress-site
php /path/to/qbixserver.php --root=. --preset=wordpress --port=8080

Drupal:

cd my-drupal-site
php /path/to/qbixserver.php --root=web --preset=drupal --port=8080

Any PHP app with a front controller:

php /path/to/qbixserver.php --root=public --port=8080

If the root directory has an index.php, all clean URLs automatically route to it (the same behavior as try_files $uri $uri/ /index.php in nginx). If there's a .htaccess, its RewriteRule and RewriteCond directives are applied.

What --preset does

Each preset sets framework-appropriate defaults: the front controller path, upload limits, memory limits, and session GC settings. You can override any of these in a JSON config file. The preset is a convenience — without it, the server still works if your .htaccess handles routing.

What gets shimmed

The server intercepts 28 PHP functions (header(), session_start(), setcookie(), ini_set(), etc.) via source transformation at include time. Your code calls header() and it works — the server captures it. Between requests, all static properties are restored from a snapshot in 0.03ms. See Compatibility for the full list.

What to watch for

Most apps work immediately. A few things to be aware of:

Platform Support

Platform Workers COW Mode
Linux x86_64, aarch64 pcntl_fork Yes — 120KB per worker Persistent or fork-per-request
macOS Intel, Apple Silicon pcntl_fork Yes Persistent or fork-per-request
FreeBSD pcntl_fork Yes Persistent or fork-per-request
Windows x64 php-cgi subprocess No Persistent workers with source-transform shimming

On Linux and macOS, the server runs thousands of COW-forked workers at ~120KB each. On Windows, pcntl doesn't exist, so the server spawns php-cgi subprocesses for process isolation. Workers are still persistent and shimmed — the same code runs, you just don't get the COW memory savings. The 28-function source transform still clears state between requests.

PHP 8.6+ (epoll/kqueue): The server auto-detects PHP 8.6's native Io\Poll API and uses epoll on Linux or kqueue on macOS for event notification — no PECL extensions needed. On older PHP versions, the server uses stream_select (which works fine, just O(n) per tick instead of O(1)). Revolt is also supported if installed.

Examples

Six example apps are included in examples/:

App What it demonstrates
todo SQLite CRUD, REST API, static HTML
counter SQLite persistence, GET/POST
chat WebSocket rooms, Socket.IO, 8 handler files
stream Server-Sent Events, AI token streaming
swarm Q::event() dispatch, cluster replication
collab Collaborative editing
php qbixserver.php --root=examples/todo/web --port=8080

Migrating from another server

Already running nginx, Apache, or Caddy? These guides show the config mapping:

Single-Binary Distribution

Package your app into one executable file — PHP runtime, web server, and all your code. The binary includes SQLite auto-provisioning: if your app bundles a .sqlite file, the server copies it to the data directory on first run and writes the framework config to point at it. No external database needed.

Supported out of the box: Qbix (detects plugins, writes local/app.json with per-plugin prefixes), Laravel (.env), Symfony (.env), WordPress (wp-config.php + wp-sqlite-db), Craft CMS, and Drupal.

Sign binaries with ECDSA P-256 keys (M-of-N threshold), publish to Sigstore Rekor for independent verification, and customize by editing the binary as a zip file.

License

MIT — use it however you want.

We proposed switch_global_context() for PHP core. While that works its way through the RFC process, the server does it in userland today.

webserver (Qbix/webserver)

Searching Database…

这是什么?一个完全用 PHP 写的 Web 服务器,用来替代 nginx + php-fpm 这套组合:单个进程就能提供静态文件、PHP 脚本、WebSocket 连接、SSE 和一个实时仪表盘。README 声称它能让现有的 PHP 项目快 10 到 100 倍。
它是用来替代我现有的 nginx + php-fpm 组合,还是要和它们一起运行?完全替代:以前需要 nginx、php-fpm、Node.js 的 Socket.IO 服务器、Redis 发布/订阅、supervisor 和 Docker(共六个进程)的实时 PHP 应用,现在只用一个进程就能跑——HTTP、WebSocket、SSE、会话、上传、静态文件和 .htaccess 全都在同一个端口上。
我怎么把现有的 PHP 应用迁移到它上面?把它指向你的项目:运行 'php qbixserver.php',可选加一个预设,比如 --preset=laravel、symfony、wordpress 或 drupal——如果你的 .htaccess 已经处理路由,也可以不加。它会读取 .htaccess,把 URL 重写到前端控制器,并兼容 28 个 PHP 函数。也有一个内置 PHP 的 4.5MB 独立可执行文件可用。
运行它需要安装什么?只需要 PHP。在 PHP 8.6+ 上它会自动检测原生 Io\Poll API,在 Linux 上用 epoll、在 macOS 上用 kqueue,不需要任何 PECL 扩展;更老的版本用 stream_select,如果装了 Revolt 也支持。不需要 nginx、php-fpm、Redis 或 Node。
请求之间的状态有什么坑吗?大多数应用可以直接跑起来,但 define() 定义的常量会在常驻 worker 之间保留,数据库迁移这类长时间运行的脚本应该用 --workers=1 或在命令行下执行,持有 C 层状态的扩展不会被重置——PDO、curl、mbstring 这些常见扩展都没问题。
它能在 Windows 上运行吗?可以,但没有内存优化技巧:Windows x64 没有 pcntl,所以服务器会启动 php-cgi 子进程——同样是常驻且带兼容层的 worker,只是省不了写时复制的内存。Linux(x86_64/aarch64)、macOS(Intel 和 Apple Silicon)和 FreeBSD 则能完整享受 COW,每个 worker 只占约 120KB。
它到底快多少?有具体数字吗?按 README 的基准测试:CPU 密集型任务达到每秒 2,294 个请求,100 个 worker 时 I/O 场景达到每秒 1,060 个请求;对比之下 php-fpm 约为每秒 400 个请求,相同内存下 I/O 场景只有每秒 78 个;大约每 1GB 内存可以支撑 5,000 个并发 PHP worker。完整测试方法见 BENCHMARKS.md。
为什么不直接用 Swoole、RoadRunner 或 FrankenPHP?Swoole 的协程只有在你把代码改写成它的协程客户端时才有效——WordPress 插件里普通的 PDO 查询和 curl_exec 调用会阻塞,永远不会让出执行权。RoadRunner 和 FrankenPHP 仍然沿用和 fpm 一样受 worker 数量限制的模型。Qbix 通过写时复制让 worker 几乎零成本,从而绕开了这两者的局限。
里面自带哪些示例应用?examples/ 目录里有六个应用:todo(带 REST API 的 SQLite 增删改查)、counter、chat(基于 Socket.IO 的 WebSocket 聊天室)、stream(Server-Sent Events、AI token 流式输出)、swarm(Q::event 事件分发、集群复制)和 collab(协同编辑)。
我能把我的应用打包成单个可执行文件发布吗?可以——代码、资源和 SQLite 数据库可以打包成一个约 4.5MB 的可执行文件,里面自带 PHP 运行时:在 Windows 上双击即可,在 Mac 或 Linux 上运行 './myapp --open',浏览器就会打开你的应用。可执行文件还支持 ECDSA P-256 的 M-of-N 签名以及发布到 Sigstore Rekor。
Creator
Qbix
Size
3.8 MB
Stars
178
Age
2mo
Commits · 90 Days