Qbix/webserver: Pure-PHP Web Server. No NGinX or PHP-FPM needed. Handle over 100x more traffic instead! - Qbix/webse

github.com · 2026-09-29 · by Long Nguyen ·github
Pure-PHP Web Server. No NGinX or PHP-FPM needed. Handle over 100x more traffic instead! - Qbix/webserver

Qbix/webserver

Pure-PHP Web Server. No NGinX or PHP-FPM needed. Handle over 100x more traffic instead! - Qbix/webserver

168 stars · 11 forks

README

⚡ Qbix Server v2 https://qbixserver.com is an all-in-one server that handles everything for you. Drop files in folders. Get real-time applications that can handle millions of users. Produce and distribute standalone binaries that run on Linux, Mac, Windows, and now iOS and Android too. Qbix Server v2 lets you build secure decentralized apps that can even work offline, over WiFi and Bluetooth. When serving millions of people, safety becomes very important. Learn why the server is written in PHP. Today's PHP ecosystem has also produced thousands of useful web frameworks including OwnCloud, WordPress, Magento, Drupal, Symfony, Laravel, and more. Qbix Server can run them all, unmodified. It also gives you a dashboard and visual control panel to manage all your apps, domains, certificates, etc. in one place. What it replaces

nginx + php-fpm Qbix Server

💾 Memory per worker 30–60MB (duplicated) ~120KB (COW, measured)

👥 Concurrent PHP (1GB) ~24 workers ~5,000 (typical)

🔒 Isolation Statics leak between requests OS-enforced: separate address space per request

🚀 Throughput (I/O, same RAM) 78 req/s (fpm/Swoole 4w) 1,060 req/s (100w)

🌐 WebSocket Needs a separate server Built in, same port

🖼️ Image resize Needs image_filter module + config ?w=300 on any image URL, auto AVIF/WebP

🔗 Peer-to-peer Not possible Encrypted mesh over BLE + Wi-Fi

📱 Mobile Not possible iOS + Android with background persistence

🔄 Data sync Not possible Bloom filters + prolly trees between peers

⚙️ 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, image resizing, .htaccess — same port, same file. No Redis, no Node, no pub/sub glue. In v2.0, the same server also discovers nearby devices over Bluetooth and Wi-Fi, establishes ECDH-encrypted sessions, routes messages through multi-hop mesh, and synchronizes data peer-to-peer with Bloom filters and prolly trees. It runs on iOS and Android alongside Linux, macOS, and Windows. A classroom of phones running the same PHP app, syncing data with no internet, no central server — one php qbixserver.php. You can also package your entire app — code, assets, SQLite database — into a single 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. How it works →

📑 Table of Contents

Documentation Quick Start Use With Your Existing Codebase Performance vs FrankenPHP and Swoole Features Server Headers For PHP Developers Configuration Platform Support Three Ways to Run Building Examples Migrating from another server Single-Binary Distribution Mesh Networking Mobile With Qbix Platform Architecture HTTP/2 Support Why PHP License

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

🗂️ Static Files ETag/304, compression, precompression cache

🖼️ Image Processing Resize with ?w=, AVIF/WebP negotiation, Save-Data, disk cache, limits

🌐 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 Running Laravel, Symfony, WordPress, Drupal unmodified: what gets rewritten and why

🏢 Frameworks All 13 supported frameworks, presets, boot adapters

🧬 --app Mode & SAPI Internals SAPI emulation, class ownership, test suites

🌐 Mesh Protocol Identity, handshake, routing, encryption

🔄 Data Sync Bloom filters, prolly trees, conflict resolution, current limits

📱 iOS & Android Running on phones, transports, permissions

📈 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 Or use the PHAR (single file, no extensions to compile): php bin/qbixserver.phar --root=./public --port=8080

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 27 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 27 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. Supported Frameworks Qbix Server ships presets and boot adapters for 13 frameworks. Every framework runs unmodified — no plugins or code changes needed.

Framework Preset Speedup vs php-builtin

Laravel --preset=laravel 3.4×

Symfony --preset=symfony —

WordPress --preset=wordpress —

Drupal --preset=drupal 4.0×

CakePHP --preset=cakephp 2.0×

CodeIgniter --preset=codeigniter —

Yii --preset=yii —

Mezzio --preset=mezzio 1.5×

Slim --preset=slim —

Joomla --preset=joomla —

Magento --preset=magento —

Nextcloud --preset=nextcloud —

ownCloud --preset=owncloud —

"—" = not benchmarked or no significant speedup on CPU-bound "hello world". Real-world I/O-bound workloads see much larger gains from the COW memory model (see Benchmarks). See FRAMEWORKS.md for full details, preset reference, and boot adapter documentation. See BENCHMARKS.md for full benchmark methodology and per-framework numbers. What to watch for Most apps work immediately. A few things to be aware of:

define() constants persist between requests in persistent workers. If a plugin defines a constant conditionally, the second request sees it already defined. Rare in practice. stream_wrapper_register() persists. Uncommon outside testing frameworks. Long-running scripts (migrations, imports) should use --workers=1 or run via CLI directly. Extensions that store C-level state (e.g. some custom PECL modules) won't reset between requests. Standard extensions (PDO, curl, mbstring) are fine.

📊 Performance Benchmarked against nginx on the same single-core container, PHP 8.3, Ubuntu 24. 13KB static file, best-of-3 runs, warm caches.

Scenario nginx Qbix Server Ratio

Sequential (c=1) 10,154 req/s 6,376 req/s 63%

Concurrent (c=10) 12,300 req/s 6,876 req/s 56%

High concurrency (c=50) 12,919 req/s 7,253 req/s 56%

Keep-alive (c=10) 26,858 req/s 19,700 req/s 73%

Keep-alive (c=50) 30,158 req/s 20,369 req/s 67%

Zero failed requests across 50,000+ requests at concurrency 50. Server never crashed.

For context: 20K req/s means the server handles 1,000 simultaneous page loads per second (assuming ~20 static assets per page), all from a single PHP process.

For PHP application workloads, the story flips — the memory and bootstrap savings matter more than static file throughput:

nginx + php-fpm: 0.1ms static file + 30ms PHP bootstrap + 5ms actual work = 35ms Qbix Server: 0.15ms static file + 0ms bootstrap + 5ms actual work = 5ms

See BENCHMARKS.md for full methodology, and the framework benchmarks for per-framework numbers.

💡 You can always put nginx, a reverse proxy, or a CDN (Cloudflare, CloudFront) in front of this for faster HTTPS and edge caching. Qbix Server handles the PHP execution, access control, and intelligent caching behind it.

⚖️ vs FrankenPHP and Swoole If you're looking beyond php-fpm, you've probably seen FrankenPHP and Swoole. Here's how they compare:

FrankenPHP Swoole Qbix Server

Language Go + C (embeds PHP) C extension for PHP Pure PHP

Install Download Go binary or Docker pecl install swoole (compiles C) php qbixserver.php — nothing to install

Architecture Worker mode (persistent) Coroutine-based (persistent) Shared-nothing with fork-after-preload

State leaks ⚠️ Possible — workers persist between requests ⚠️ Possible — must manage globals carefully ✅ Impossible — each request gets a clean fork

PHP compatibility Most code works, some edge cases Many extensions incompatible, blocking I/O breaks coroutines ✅ 100% — standard PHP, nothing unusual

Memory safety Go runtime + PHP = complex interaction C extension = segfault risk PHP only = memory-safe by default

Access control No X-Accel-Redirect equivalent Manual implementation ✅ Built-in X-Accel-Redirect

Component cache No No ✅ X-Cache-Tree — sub-page invalidation

Early hints / 103 ✅ Yes No Via amphp

HTTP/2 ✅ Built-in (Caddy) ✅ Built-in ✅ Via amphp

WebSocket Via Mercure ✅ Built-in ✅ Built-in

The shared-nothing advantage FrankenPHP and Swoole keep PHP workers alive across requests. This is fast, but it means global state, static variables, database connections, and in-memory caches persist between unrelated requests. This causes subtle bugs: // This leaks between requests in FrankenPHP/Swoole: class UserService { private static ?User $cached = null; public static function current(): User { if (!self::$cached) { self::$cached = User::fromSession(); } return self::$cached; // Returns previous user's data! } } Every PHP framework, library, and snippet that uses static variables, singletons, or global state becomes a potential security hole. You have to audit everything. Qbix Server avoids this entirely. Workers fork from a preloaded parent, so they inherit loaded classes and parsed config (read-only, shared via copy-on-write). But each request runs in its own process — when it's done, everything is gone. No state leaks. No audit needed. Your existing PHP code works exactly as it does on php-fpm. The "just PHP" advantage FrankenPHP requires Go tooling to build or a pre-built binary that bundles Caddy. Swoole requires compiling a C extension, which can conflict with other extensions and doesn't work on all hosting environments. Qbix Server is a PHP file. If you can run php -v, you can run the server. It uses standard PHP extensions (sockets, pcntl) that come pre-installed on most systems. There's no compilation step, no foreign runtime, no binary compatibility issues.

FrankenPHP

docker pull dunglas/frankenphp # 150MB+ image, or build from Go source

Swoole

pecl install swoole # compiles C, may fail on some systems

Then edit php.ini, restart php...

Qbix Server

php qbixserver.php --port=8080 # done When to choose what Choose FrankenPHP if you want Caddy's ecosystem (automatic HTTPS, HTTP/3) and don't mind Go as a dependency. Good for Laravel projects that already use Octane. Choose Swoole if you need coroutines for high-concurrency I/O (thousands of simultaneous HTTP client requests, database queries). Good for async-heavy microservices. Choose Qbix Server if you want shared-nothing safety, zero-install deployment, access-controlled file serving, component-level cache invalidation, and full compatibility with existing PHP code. Good for apps that serve pages (not just APIs), need fine-grained caching, and want the simplest possible deployment.

✨ Features

Category What you get

Static files ETag, 304 Not Modified, Last-Modified, MIME type detection, in-memory response cache

Keep-alive HTTP/1.0 and 1.1, TCP_NODELAY, configurable limits

HTTP/2 Via amphp — multiplexed streams, header compression, TLS (optional)

PHP execution .php files in document root run in-process or via pre-fork worker pool

Compression On-the-fly gzip/brotli + pre-compressed .gz/.br siblings

WebSocket RFC 6455 upgrade on any path

Dashboard Live stats at /Q/dashboard — request rates, memory, status codes

Health check JSON at /Q/health — for load balancers and monitoring

Control panel Password-protected at /Q/panel — manage apps and scripts

Rate limiting Per-IP with configurable windows and burst limits

Security Path traversal blocked, dotfiles blocked, 431 for oversized headers, 400 for malformed requests

Graceful shutdown SIGTERM/SIGINT drain in-flight requests before closing

TLS Optional HTTPS with auto-certbot or manual certs

Logging Colored terminal output + file-based access logs

Access control X-Accel-Redirect support — PHP enforces access, server serves the file

Component cache X-Cache-Tree headers — invalidate parts of a page, not the whole thing

Image processing Resize with ?w=, automatic AVIF/WebP negotiation, disk cache

Framework presets Built-in presets for 13 frameworks — Laravel, Symfony, WordPress, Drupal, and more

Mesh networking Encrypted P2P over BLE + Wi-Fi with multi-hop routing

Data sync Bloom filter + prolly tree sync between peers

🔒 Server Headers — What Your PHP Can Send Qbix Server understands special response headers from your PHP scripts. These are the same headers nginx understands (like X-Accel-Redirect) plus new ones for component-level caching. Your PHP sends them with header(), the server acts on them. Quick reference

Header What it does Example

Cache-Control Server caches the response, serves without running PHP header('Cache-Control: public, max-age=300');

X-Accel-Redirect Server streams a file after PHP checks access header('X-Accel-Redirect: /uploads/private/doc.pdf');

X-Cache-Tree Registers page components with content hashes header('X-Cache-Tree: ' . json_encode([...]));

X-Cache-Deps Maps components to data dependency keys header('X-Cache-Deps: ' . json_encode([...]));

X-Cache-Invalidate Marks dependency keys as stale header('X-Cache-Invalidate: ' . json_encode([...]));

X-Cache-Stale Marks specific components as needing re-render header('X-Cache-Stale: feed,sidebar');

All of these are standard PHP header() calls. No SDK, no framework needed. The server strips them before sending the response to the client. Access-controlled static files With a typical server, your uploaded files sit at public URLs. Anyone with the link can access them — and share the link with others. The usual workaround is "unguessable" URLs, which are just security through obscurity. X-Accel-Redirect lets your PHP check access, then tells the server to serve the file directly — fast, streamed, with no public URL exposed: <?php // web/download.php — access-controlled file serving session_start();

$fileId = $_GET['id'] ?? ''; $userId = $_SESSION['user_id'] ?? null;

// Your access control logic if (!$userId || !userCanAccess($userId, $fileId)) { http_response_code(403); echo 'Access denied'; exit; }

// Tell the server to serve the file directly. // The client never sees the real path. header("X-Accel-Redirect: /uploads/private/{$fileId}"); header("Content-Disposition: attachment; filename=\"document.pdf\"");

// The server takes over from here — streams the file // with correct Content-Type, ETag, compression, etc. // Your PHP process is already done. No public URL for the file. No redirect the user can bookmark. The server streams the file after your PHP has verified access and exited. Component-level cache invalidation Most caching systems cache whole pages. When anything changes, you throw away the entire page and re-render everything. Qbix Server can cache individual components and only re-render what changed. Step 1: Register components when rendering a page <?php // web/community.php — a page with three components

$feedHtml = renderFeed($communityId); $sidebarHtml = renderSidebar($communityId); $membersHtml = renderMembers($communityId);

// Tell the server about the component tree and what data each depends on header('X-Cache-Tree: ' . json_encode([ 'l' => [ 'feed' => md5($feedHtml), 'sidebar' => md5($sidebarHtml), 'members' => md5($membersHtml), ] ]));

header('X-Cache-Deps: ' . json_encode([ 'feed' => ["community/{$communityId}/feed"], 'sidebar' => ["community/{$communityId}/about"], 'members' => ["community/{$communityId}/participants"], ]));

header('Cache-Control: public, max-age=300'); echo $feedHtml . $sidebarHtml . $membersHtml; Step 2: Invalidate when data changes <?php // web/post.php — user posts to the feed saveNewPost($communityId, $content);

// Tell the server which dependency key changed header('X-Cache-Invalidate: ' . json_encode([ "community/{$communityId}/feed" ]));

// The server walks its dependency graph: // community/123/feed → page /community/123 component 'feed' // Only 'feed' is stale. Sidebar, members = still cached. // Next request re-renders only the feed component.

echo json_encode(['ok' => true]); The server maintains a Merkle tree of component hashes. When a dependency key is invalidated, it walks the tree to find exactly which components on which pages are affected. Everything else is served from the in-memory cache.

📂 For PHP Developers — The Micro-Framework Qbix Server isn't just a static file server with PHP bolted on. It's a micro-framework where you drop files into conventional directories and things just work — classes autoload, events fire handlers, views render templates. No configuration needed for the basics. Project layout myproject/ ├── qbixserver.php

source

github.com · 168 links

Comments (0)

No comments yet