Qbix Server is a complete web server in a single executable. No nginx, no Apache, no Docker. Download, run, publish.
The built-in dashboard at /Q/dashboard shows live stats, request logs, and lets you manage your apps.
No terminal experience needed. Just download, double-click, and go.
One file, around 15MB. It contains everything: the PHP runtime, the web server, SQLite for data, and the Qbix framework. No other software to install. Drag it wherever you like — your Desktop, Applications, anywhere.
Double-click the file (or run ./qbix-server from a terminal). It opens a management page in your browser where you can create apps, see live traffic, and configure domains. Each app gets its own folder you can open in Finder, Explorer, or your text editor.
For a public site, point your domain's DNS to your server (or use Cloudflare for free HTTPS). The dashboard lets you configure which domain goes to which app. For local development, it just works on localhost.
| Option | Setup | Best for |
|---|---|---|
| Cloudflare | Point DNS, flip a switch | Easiest. Free. Works anywhere. |
| AWS / GCP / Azure | Load balancer console | Already on cloud infrastructure |
| Caddy reverse proxy | One terminal command | Self-hosted, auto Let's Encrypt |
| Built-in TLS | Set domain in config | Dev servers, personal sites |
Your server runs on your phone. Share it with anyone nearby — no WiFi, no cloud, no app download required.
One person creates a Personal Hotspot. Others connect to it and scan a QR code. Everyone gets the full web interface in their browser — no app needed, no internet, no setup.
At home, at a café, at a conference — if multiple Qbix phones are on the same WiFi, they find each other automatically via Bonjour/mDNS. No QR code needed. Peers just appear.
People without the app can still scan a QR code and use the full web interface in their browser.
If the WiFi blocks local connections (some hotels do this), the app detects it and suggests creating a Personal Hotspot instead.
When someone downloads the app, their phone becomes a node. Messages propagate like gossip — if Alice tells Bob, and Bob walks past Carol, Carol gets Alice's messages without ever connecting to Alice directly.
Works even with brief, intermittent contact. Walking past someone for a few seconds is enough to sync a day's worth of messages over Bluetooth.
When internet is available later, everything syncs upstream — the offline mesh and the online world reconnect seamlessly.
The native client for the Qbix Platform. 7 million downloads across 100+ countries. No ads. No data harvesting.
Groups turns the Qbix server you just learned about into a community management platform — running on your phone. Community leaders send messages from their own phone using their own SMS and email. The server never sees your contacts. This isn't a policy — it's the architecture.
Contact names, phone numbers, emails — all stay in local storage. When you send a link, the app generates opaque tokens (random strings). The mapping between tokens and real people never leaves your device.
The server sees tokens and URLs. It tracks which tokens clicked which links, and provides analytics. But it has no API endpoint that accepts a name, phone number, or email in the link-tracking path. It cannot access what it was never given.
The Qbix Server you're reading about is what runs inside the Groups app. When you create a Personal Hotspot and others scan your QR code, they're connecting to the same PHP server — serving the same web interface, handling the same Streams, running the same privacy-preserving token system.
The Qbix Platform is an open-source framework for building social apps and communities.
Source code, issues, and pull requests. Star the project to follow updates.
Full documentation: plugins, Streams, Users, tools, events, and the Q framework.
git clone https://github.com/Qbix/Platform.git cd Platform php scripts/Q/webserver.php --workers=4
Requires PHP 8.1+ with dom, curl, mbstring, pcntl, sqlite3 extensions.
# One-time build (uses static-php-cli) ./scripts/Q/build.sh # Output: qbix-server (~15MB) # Contains: PHP + SQLite + Q framework + your plugins # Runs on any machine of the same OS/arch, no PHP needed
Everything is a plugin: Users, Streams, Communities, IndieWeb. Create your own with handlers, tools, views, config, and DB schemas.
Webmention, microformats, RSS/Atom feeds, endpoint discovery. The only community platform with native IndieWeb support.
Real-time on the same port. Q_WebSocket handles RFC 6455 natively. No socket.io dependency — just the browser's built-in WebSocket API.
Set breakpoints in your IDE, trigger with a cookie. Only that request pauses — the rest of the site keeps running. Better than php-fpm.
The design decisions that make this possible, explained for programmers.
Nginx does three things: accept connections, serve static files, and proxy to PHP. We do all three in PHP. Static files are served with readfile() — same syscall pattern as nginx (stat() → open() → read() → write()). With HTTP/1.1 keep-alive, a page with 20 assets uses 1 TCP connection, not 20. Pre-compressed .gz / .br files are served directly (like nginx's gzip_static). For a personal or small community site, the throughput difference is unmeasurable.
PHP-fpm has two bad options: reload everything per request (~20ms overhead) or reuse processes with dirty state (memory leaks, secret leaks between requests). We do something better:
The parent preloads all PHP classes into memory before forking. Children inherit everything via the kernel's copy-on-write mechanism — the class definitions are already in memory, no autoloading needed. Each child handles exactly one request, then exits. No memory leaks. No secret leaks between requests. And it's faster than php-fpm's "reload everything" mode because the classes are already loaded.
We deliberately chose not to reuse worker processes across requests. You could theoretically use PHP's Reflection API to reset every static variable, close every DB connection, flush every buffer — but you'd still miss edge cases. A process that served User A's authenticated session should never carry any residue when serving User B. The only way to guarantee that is exit(). The ~0.5ms fork cost is nothing compared to the security and reliability guarantees.
The IndieWeb plugin stores everything as JSON files. Webmentions? A directory of JSON files keyed by URL hash. Feeds? Generated from HTML using the same hash/diff engine the framework uses for static asset cache-busting. For a personal site receiving a few dozen webmentions a day, a directory of JSON files is as fast as MySQL and infinitely simpler. SQLite is included for plugins that need relational queries, but the core server doesn't require it.
We use static-php-cli to compile PHP as a fully static binary (musl libc, no dynamic dependencies) with exactly the extensions needed. Then phpmicro glues the PHP interpreter to a .phar archive containing the Q framework and your app:
micro.sfx + qbix-app.phar = qbix-server (one file, ~15MB) Compare: Node.js SEA ~50-90MB Deno compile ~70-100MB Go binary ~10-20MB qbix-server ~10-15MB ← competitive with Go
The binary runs on any machine of the same architecture. No PHP installation, no package manager, no container runtime. Copy the file, run it.
File access has three distinct levels, and it's important not to confuse them:
| Level | What happens | Example |
|---|---|---|
| Blocked (403) | Can't access at all by URL. Server returns Forbidden. | /config/, /classes/, /.env, dotfiles |
| Unlisted (no directory index) | Directory listings are off by default. Only paths matching regexes in Q.web.indexed.paths get listings. Files are still accessible by direct URL. |
Default: only /img/ is listed. Add more in config. |
| Access-controlled (X-Accel-Redirect) | Files live outside the web root. PHP checks permissions, then tells the server to serve the file. The URL never maps to the file directly. | User uploads, paid content, private documents |
X-Accel-Redirect is how you do real access control. The pattern: put protected files outside the web root (e.g. /app/files/), write a PHP script that checks permissions, then set the X-Accel-Redirect header pointing to the internal path. The server intercepts the header (never sends it to the client), resolves the path via configured mappings, and serves the file with readfile(). PHP never buffers the file body — it just says "this user is allowed, serve this file" and the server does the efficient I/O.
// In your access-check script:
$authorized = check_user_permissions($filename);
if ($authorized) {
header("Content-Type: " . mime_content_type($filename));
header("X-Accel-Redirect: /internal/uploads/" . $relativePath);
} else {
throw new Users_Exception_NotAuthorized();
}
Configure the internal mapping in your app config:
{
"Q": {
"webserver": {
"accel": {
"mappings": {
"/internal/uploads": "/var/app/files/uploads"
}
}
}
}
}
The server maps /internal/uploads/photo.jpg to /var/app/files/uploads/photo.jpg on disk, checks that it doesn't escape the mapped directory (path traversal protection), and serves it with ETag, compression, and proper headers. The /internal/ prefix is never directly accessible — only reachable via the X-Accel-Redirect header from PHP.
The server process should run as a non-root user (the systemd unit defaults to www-data). Set file permissions so the web directory is readable but not writable by the server, and the files/config directories are writable only where needed. The --systemd flag generates a unit file with ProtectSystem=strict and explicit ReadWritePaths.
The built-in server speaks HTTP/1.1 with keep-alive — fast enough for most sites. For HTTP/2 (HPACK compression, stream multiplexing, flow control), just install amphp:
composer require amphp/http-server amphp/socket revolt/event-loop psr/log
That's it. Next time you start the server, it auto-detects amphp and prints → amphp detected — HTTP/2 + 4 prefork workers. Same command, same config, same routing. The architecture is the same either way — only the connection layer changes:
stream_select HTTP/1.1 | Amphp: Revolt HTTP/2This is strictly better than both alternatives. Better than nginx + php-fpm: no IPC socket overhead, the parent serves cache hits and static files without touching a worker, and preloaded classes are inherited via fork() not reloaded per request. Better than pure async PHP: guaranteed clean state per request — no leaked sessions, no stale static variables, no memory growth. Each child handles one request and exits. The fork costs ~0.5ms. The guarantee is worth it.
PHP can do TLS (we support it for dev/personal sites), but for production the math is clear: cloud edge networks (Cloudflare, AWS CloudFront) and reverse proxies (Caddy, nginx) do TLS in optimized C/Go/Rust with hardware AES-NI and kernel sendfile(). They're 2-3× faster per byte and add HTTP/3 for free. The server speaks HTTP internally and lets the upstream handle encryption.
# HTTP/1.1, in-process (dev, zero deps) php scripts/Q/webserver.php # HTTP/1.1, prefork workers (production, zero deps) php scripts/Q/webserver.php --workers=4 # HTTP/2, prefork workers (production, best of both) composer require amphp/http-server amphp/socket revolt/event-loop psr/log php scripts/Q/webserver.php --workers=4 → amphp detected — HTTP/2 + 4 prefork workers
| Component | What handles it | Why |
|---|---|---|
| TLS termination | Cloudflare / Caddy / ALB | Hardware AES, HTTP/2+3, auto-certs |
| Cache hits | Parent process (APCu + file) | No fork, instant response |
| Static files | Parent process (readfile) | Keep-alive, ETag/304, gzip, brotli |
| PHP scripts | Forked workers (shared-nothing) | Pristine state, preheated classes |
| WebSocket | Parent process (Q_WebSocket) | Same port as HTTP, RFC 6455 |
| File access control | X-Accel-Redirect | PHP checks auth, server does I/O |
| HTTP/2 | amphp (optional, auto-detected) | Multiplexing, HPACK, same pool |
| Config reload | Q_FileCache (mtime checks) | Hot reload without restart |
| Process management | systemd / PID file / SIGHUP | Auto-restart, graceful reload |
| Total binary size | ~15MB. Zero dependencies. HTTP/2 optional via composer. | |
Each row eliminates a dependency that previously required money, expertise, or centralized infrastructure.
| Layer | What we built | What it replaces |
|---|---|---|
| 📦 Binary | static-php-cli + phpmicro | Server installation |
| 🌐 Web server | Q_WebServer (prefork) | nginx + php-fpm |
| ⚡ Real-time | Q_WebSocket | socket.io + Node.js |
| 🗜️ Caching | Q_WebServer_Cache | Varnish / Redis |
| 🔒 TLS | Certs + upstream proxy | certbot + nginx |
| 🎛️ Management | Q_WebServer_Panel | SSH + CLI |
| 📡 Discovery | mDNS + BLE + QR | DNS + domain registration |
| 📶 Transport | Hotspot + WiFi + BLE | The internet itself |
| 🔄 Federation | Scuttlebutt gossip | ActivityPub servers |
| 🔐 Privacy | Opaque tokens | Trust-me policies |
| 📱 App distribution | QR → browser → app | App Store discovery |
The bottom of the stack is the most radical: the transport layer itself becomes peer-to-peer. You don't need the internet to have a social network. You need proximity.
What you get out of the box, no additional software needed.
HTTP/1.1 persistent connections. A page with 20 assets uses one TCP connection. 100 requests per connection, 15s idle timeout.
Real-time request log, memory usage, worker status, status code breakdown. Updates instantly via WebSocket. No refresh needed.
Gzip on-the-fly for text responses. Pre-compressed .gz and .br file support (like nginx gzip_static). Automatic content negotiation.
Listings off by default. Whitelist paths with regex in config. Dotfiles always blocked (403). True access control via X-Accel-Redirect.
Responsive file browser with media previews (images, video, audio). Clean design that works on phones. Respects privacy rules.
PHP checks permissions, then tells the server "serve this file" — no PHP buffering. Same pattern as nginx's internal redirect.
Apache combined format. Automatic rotation at 10MB. Works alongside the live dashboard — files for archival, WebSocket for real-time.
/Q/health returns JSON stats for load balancer health checks. Worker count, memory, uptime, request totals.
Edit config files while running — changes are detected by mtime and applied automatically. SIGHUP forces an immediate reload.
Extracts real client IP from X-Forwarded-For, real protocol from X-Forwarded-Proto. Configurable trusted proxy list.
Drop a file.html.headers next to file.html to add custom HTTP headers per-page. One "Key: Value" per line. Generated by static.php.
--systemd generates a production unit file with security hardening. PID file, auto-restart, SIGHUP reload.