返回资讯中心
外部精选
软件工程
#开发者实时流

Exponential Velocity – UltraFast Modern Pure PHP Webserver Built for Production

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

Hacker News 最新thekracker2026/10/27 · 昨天30 分钟阅读

以下正文同步自 Hacker News 最新,版权归原站所有,已转换为易读排版。

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.

Velocity is a PHP web server for real production work. It began as a fork of the Qbix web server engine, and the two projects have since drifted far apart: Velocity keeps the engine's copy-on-write worker model, WebSockets and live dashboard — and your PHP still runs unmodified — and adds everything a production site needs: the packaging, the control plane, the security and the site-operations tooling. The program names (qbixserver, qbixctl, qbixconsole), the QBIX_* environment variables and the /etc/qbix tree keep the engine's names, so existing scripts and installations keep working.

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.

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.

Instead of making each worker do more, Velocity 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 pays only for the pages it writes to after the fork. Measured as private memory (not RSS, which counts shared pages once per worker): about 1.3–1.9 MB per worker with nothing loaded, about 10 MB for a full CMS. So the same 4GB that gives fpm 80 workers gives Velocity a few hundred CMS workers, or around two thousand small ones -- within the event loop's ceiling of about 1,000 workers per pool (what a worker costs).

Persistent workers (default) — workers stay alive across requests. Between each request, a Reflection-based snapshot restores all static properties, and globals and the shimmed functions' state are reset: about 0.5 ms for a small application, 4–5 ms for a CMS with ~600 classes -- around 1.5% of a typical page render. 44 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 a few MB instead of 50MB, so you can run many times 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.

nginx + php-fpm Velocity

💾 Memory per worker 30–60MB (duplicated) 1.3–1.9 MB bare, ~10 MB full CMS (COW, private, measured)

👥 Concurrent PHP (1GB) ~24 workers ~530 bare, ~90 full CMS (under ~1,000 per pool with stream_select)

🔒 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-Q-Cache-Tree — per-component

⚙️ Setup nginx + fpm pools + sockets php sbin/qbixserver.php

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

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.

Velocity 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 →

Topic What it covers

📋 Changelog Every change worth reading, newest first. A release links here rather than repeating itself

🏎️ Why Not php-fpm? COW memory model, comparison with Swoole and FrankenPHP

📋 Requirements & Extensions The PHP versions built, every extension the server provides by tier and variant, databases, and each form of distribution's exceptions

🧩 Extensions: Check & Build qbixctl ext:check, install hints per system, database add-ons, planning and building a variant, the source kit

🔐 HTTPS & Certificates Your own certificates (files, archives, .p12), Let's Encrypt built in, self-signed fallback, live renewal

🔒 Server Headers Cache-Control, X-Q-Cache-Tree, X-Accel-Redirect, ETag

🗃️ Response Cache What is kept and for how long, the generation marker, every setting

🛡️ Security What is refused and why: document-root containment, request framing, HTTP/2 frame validation, header injection

🛂 Control Panel: signing in The default password panel, the first sign-in, locking the default down, a forgotten password

🔑 Panel Passwords bcrypt storage, the password rules with examples, the default key and its forced change, lockout, qbixctl panel:password

🔐 Two-Factor Auth Optional TOTP second factor for the panel (off by default): enrollment, recovery codes, the config flag, qbixctl panel:2fa

🌐 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

🗂️ Configuration Layout /etc/qbix laid out like /etc/apache2, load order, overlays, distributions

⌨️ Console & Command Line qbixserver.php options, qbixconsole commands, qbixctl, option styles

📦 Running & Building Source, phar, binary. Building static binaries. Requirements

🎩 PHP 8.0 & Red Hat Enterprise Linux PHP 8.0 to 8.5: why 8.0 works, what differs there, installing on RHEL 8 and 9, extensions, systemd, SELinux, verifying

📀 Binaries & Signing Pack apps, manage like zip, ECDSA M-of-N signing, Rekor, platform signing

🏗️ Architecture Persistent workers, COW, execution model, mental model, benchmarks

🏭 Workers & Pool Pool size, static and dynamic pools, when a worker is replaced, reload

🧭 Lessons What persists between requests, what workers inherit, memory, opcache, TLS in a forked child

⏳ Time-Consuming Lessons The problems that took longest to diagnose: symptom, cause, how to recognise it, fix

📊 Dashboard & Panel Live stats, control panel tabs

🖥️ Q Shell Drop-down console on every server view: tiers, jobs, sudo, REST API

🎨 Designs Restyle the dashboard, panel, docs, listing and error pages from designs/

🚀 Deploy & Federation Rsync deploy, cluster replication, inter-server trust

🔍 API Discovery OpenAPI, MCP, qbix.json, HTTP/2

🧩 Compatibility SAPI emulation, 44 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/se7enxweb/exponential-velocity cd exponential-velocity php sbin/qbixserver.php

Download a binary

Self-contained: PHP is inside the binary, so there is nothing to install and no version of PHP on the machine to conflict with.

Linux x86_64

curl -LO https://github.com/se7enxweb/exponential-velocity/releases/latest/download/qbixserver-linux-x86_64 chmod +x qbixserver-linux-x86_64 && ./qbixserver-linux-x86_64

Linux aarch64 -- also what a Raspberry Pi 4 or 5 runs

curl -LO https://github.com/se7enxweb/exponential-velocity/releases/latest/download/qbixserver-linux-aarch64 chmod +x qbixserver-linux-aarch64 && ./qbixserver-linux-aarch64

Windows x64

curl -LO https://github.com/se7enxweb/exponential-velocity/releases/latest/download/qbixserver-windows-x64.exe qbixserver-windows-x64.exe

All downloads →

The binaries exist for convenience, not necessity. qbixserver.phar is the same server and needs nothing but a PHP 8.0 or later interpreter, which is why it runs on far more than the handful of platforms we can build binaries for.

curl -LO https://github.com/se7enxweb/exponential-velocity/releases/latest/download/qbixserver.phar php qbixserver.phar --root=./web

Where the programs are: bin/ and sbin/

From 0.0.4.41 the tree keeps its programs the way UNIX and Linux systems do (the Filesystem Hierarchy Standard): the daemon and the commands that administer it in sbin/, the commands any user runs in bin/, and source code with the sources.

uwebserver from 0.0.4.42 on listens on 127.0.0.1:8000 by default (it used to take every interface at 8080), serves as root only with --user or --allow-root, and starts nothing on an unknown option. Nothing in a site starts it, so a site is unaffected; see Upgrading.

The packages install qbixserver, qbixctl and qbixconsole in /usr/sbin and keep links at /usr/bin; the systemd unit starts /usr/sbin/qbixserver. From 0.0.4.42 they install the shell too, as /usr/bin/vc-qshell (a link to bin/qshell.php; qshell is the name of Qiniu's command-line tool), and the container image as /usr/local/bin/vc-qshell.

What makes this distribution different is that nothing had to change to get there. Every former path is a forwarder that runs the new program in the same process, so a systemd unit, a cron job, a Composer vendor/bin link, a CMS's control script or a pkill -f 'qbixserver.php' pattern keeps working, and a server started by an old path is still found, reloaded and restarted by qbixctl exactly as it was started. Standard output, input and the exit status are the new program's; the only sign is one line on standard error, and only at a terminal (QBIX_MOVED_QUIET=1 hides it). The full table of what moved, and why, is in docs/layout.md.

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 44 functions shimmed so static variables, sessions, and headers work correctly between requests.

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

Symfony:

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

WordPress:

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

Drupal:

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

Exponential (the eZ Publish 4 legacy line):

cd my-exponential-site php /

正文由 探How 从来源站点 RSS 同步,内容未经改写;遇到排版缺失或需要图片、视频时请以原文为准。

打开原文
去社区讨论 →