Open Source · Zero Dependencies · ~15MB

Your website.
Your server.
One file.

Qbix Server is a complete web server in a single executable. No nginx, no Apache, no Docker. Download, run, publish.

Download for macOS Linux Windows
~15MB · macOS arm64 / x86_64 · No installation needed
$ ./qbix-server --port=8080

┌──────────────────────────────────────┐
Qbix Web Server
├──────────────────────────────────────┤
http://localhost:8080
PHP: 4 workers (prefork)
Dashboard: /Q/dashboard
├──────────────────────────────────────┤
Ctrl+C to stop
└──────────────────────────────────────┘

Manage everything from your browser

The built-in dashboard at /Q/dashboard shows live stats, request logs, and lets you manage your apps.

Qbix Server — localhost:8080/Q/dashboard Qbix Server ● running 2h 15m REQUESTS 12,847 4.2/s WORKERS 3/4 idle MEMORY 12.3 MB STATUS 12,340 ok 492 redir 15 err Your Apps + New App My IndieWeb Site myblog.com → :8080 HTTPS via Cloudflare Config Open 📂 Open in VS Community Forum forum.example.com → :8081 Built-in TLS (certbot) Config Open 📂 Open in VS Test App localhost → :3000 stopped Start Live Requests live 14:23:01 200 GET /style.css 0.3ms 14:23:02 200 GET / 12ms 14:23:05 200 POST /webmention 45ms 14:23:05 304 GET /favicon.ico 0.1ms

Get started in 3 steps

No terminal experience needed. Just download, double-click, and go.

Download for your system

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.

Run it

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.

Point your domain

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.

HTTPS options — pick what fits you

OptionSetupBest for
CloudflarePoint DNS, flip a switchEasiest. Free. Works anywhere.
AWS / GCP / AzureLoad balancer consoleAlready on cloud infrastructure
Caddy reverse proxyOne terminal commandSelf-hosted, auto Let's Encrypt
Built-in TLSSet domain in configDev servers, personal sites

Works without the internet

Your server runs on your phone. Share it with anyone nearby — no WiFi, no cloud, no app download required.

QR 172.20.10.1 Alice (Hotspot) Safari Bob Safari Carol Dave No internet needed

On a plane, bus, or anywhere

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.

1. Create hotspot on your phone
2. Qbix shows a QR code on screen
3. Others scan → Safari opens → done
4. Next time, devices auto-reconnect
WiFi Qbix Alice Qbix Bob auto-discovered Safari Carol (QR) Bonjour discovers peers automatically

On shared WiFi

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.

A B C D E F joining Messages gossip through the mesh — everyone gets everything

The mesh grows

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.

✈️
On a plane
Hotspot + QR. No paid WiFi needed. Share stories, plan the trip, play games.
🏕️
Camping / rural
Bluetooth mesh syncs between phones. A day's messages fit in one second of contact.
🎓
Classroom
Teacher's phone runs the community. Students join via QR. No school WiFi, no accounts, no data leaves the room.
🏘️
Neighborhood
Old phones or $30 Raspberry Pis form a permanent local mesh. Works even if ISP is down.

The Groups App

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.

Download for iOS Android — coming soon
My Communities 🏠 Neighborhood Watch 23 members · 3 new Poker Night 8 members · Friday 7pm Soccer Team 15 members · Game Sun 2pm 🔒 Your contacts stay on your phone Server sees opaque tokens, never PII Groups Contacts Settings

Privacy is the architecture, not a promise

📱 On your phone

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.

☁️ On the server

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.

Read the full technical architecture → Privacy-preserving link tracking, identity without phantom users, intent verification

Groups + Qbix Server = the full stack

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.

Desktop / Raspberry Pi
Run the binary directly. Manage via web panel. Permanent community server.
iPhone / iPad
Groups app from the App Store. Same server, native UI, mesh via Multipeer Connectivity.
Android
Coming soon. Same server binary via Termux today. Native app in development.
Browser only
Scan a QR code. Full web interface in Safari/Chrome. No install needed.

For developers

The Qbix Platform is an open-source framework for building social apps and communities.

Running from source

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.

Building the single binary

# 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
🔌

Plugin architecture

Everything is a plugin: Users, Streams, Communities, IndieWeb. Create your own with handlers, tools, views, config, and DB schemas.

🌐

IndieWeb built-in

Webmention, microformats, RSS/Atom feeds, endpoint discovery. The only community platform with native IndieWeb support.

🔄

WebSocket included

Real-time on the same port. Q_WebSocket handles RFC 6455 natively. No socket.io dependency — just the browser's built-in WebSocket API.

🐛

xdebug per-worker

Set breakpoints in your IDE, trigger with a cookie. Only that request pauses — the rest of the site keeps running. Better than php-fpm.

How it works

The design decisions that make this possible, explained for programmers.

Why pure PHP replaces nginx

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.

Why fork-per-request beats php-fpm

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:

Parent process
Loads Q.inc.php + all classes (once)
Event loop: static files, keep-alive, WebSocket, dashboard
↓ fork() — ~0.5ms, copy-on-write ↓
Worker 0
1 request
then exit
Worker 1
1 request
then exit
Worker 2
1 request
then exit
Worker 3
1 request
then exit
Each worker inherits all loaded classes via copy-on-write (no file I/O)
Handles one request with pristine state, then exits → guaranteed clean
↑ re-fork immediately ↑

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.

Why shared-nothing wins

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.

No database needed

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.

How the single binary works

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.

Securing files: three levels

File access has three distinct levels, and it's important not to confuse them:

LevelWhat happensExample
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.

OS-level permissions

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.

HTTP/2 with amphp (transparent upgrade)

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:

Connection layer (evented, async, non-blocking)
Built-in: stream_select HTTP/1.1  |  Amphp: Revolt HTTP/2
Handles: keep-alive, cache hits, static files, WebSocket, dashboard — no fork needed
↓ PHP request? → Pool::dispatch() ↓
Worker 0
1 request
then exit
Worker 1
1 request
then exit
Worker 2
1 request
then exit
Worker 3
1 request
then exit
Same prefork pool in both modes. Children inherit preloaded classes via COW.
Pristine state per request. Amphp adds HTTP/2 — the execution model stays identical.

This 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.

Three modes, one command

# 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

The full production stack

ComponentWhat handles itWhy
TLS terminationCloudflare / Caddy / ALBHardware AES, HTTP/2+3, auto-certs
Cache hitsParent process (APCu + file)No fork, instant response
Static filesParent process (readfile)Keep-alive, ETag/304, gzip, brotli
PHP scriptsForked workers (shared-nothing)Pristine state, preheated classes
WebSocketParent process (Q_WebSocket)Same port as HTTP, RFC 6455
File access controlX-Accel-RedirectPHP checks auth, server does I/O
HTTP/2amphp (optional, auto-detected)Multiplexing, HPACK, same pool
Config reloadQ_FileCache (mtime checks)Hot reload without restart
Process managementsystemd / PID file / SIGHUPAuto-restart, graceful reload
Total binary size~15MB. Zero dependencies. HTTP/2 optional via composer.

What it replaces

Each row eliminates a dependency that previously required money, expertise, or centralized infrastructure.

LayerWhat we builtWhat it replaces
📦 Binarystatic-php-cli + phpmicroServer installation
🌐 Web serverQ_WebServer (prefork)nginx + php-fpm
⚡ Real-timeQ_WebSocketsocket.io + Node.js
🗜️ CachingQ_WebServer_CacheVarnish / Redis
🔒 TLSCerts + upstream proxycertbot + nginx
🎛️ ManagementQ_WebServer_PanelSSH + CLI
📡 DiscoverymDNS + BLE + QRDNS + domain registration
📶 TransportHotspot + WiFi + BLEThe internet itself
🔄 FederationScuttlebutt gossipActivityPub servers
🔐 PrivacyOpaque tokensTrust-me policies
📱 App distributionQR → browser → appApp 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.

Everything included

What you get out of the box, no additional software needed.

Keep-alive connections

HTTP/1.1 persistent connections. A page with 20 assets uses one TCP connection. 100 requests per connection, 15s idle timeout.

📊

Live dashboard

Real-time request log, memory usage, worker status, status code breakdown. Updates instantly via WebSocket. No refresh needed.

🗜️

Compression

Gzip on-the-fly for text responses. Pre-compressed .gz and .br file support (like nginx gzip_static). Automatic content negotiation.

🔒

Directory listing control

Listings off by default. Whitelist paths with regex in config. Dotfiles always blocked (403). True access control via X-Accel-Redirect.

📁

Pretty directory listings

Responsive file browser with media previews (images, video, audio). Clean design that works on phones. Respects privacy rules.

🔁

X-Accel-Redirect

PHP checks permissions, then tells the server "serve this file" — no PHP buffering. Same pattern as nginx's internal redirect.

📝

Access logging

Apache combined format. Automatic rotation at 10MB. Works alongside the live dashboard — files for archival, WebSocket for real-time.

🏥

Health endpoint

/Q/health returns JSON stats for load balancer health checks. Worker count, memory, uptime, request totals.

🔄

Hot config reload

Edit config files while running — changes are detected by mtime and applied automatically. SIGHUP forces an immediate reload.

🛡️

Reverse proxy aware

Extracts real client IP from X-Forwarded-For, real protocol from X-Forwarded-Proto. Configurable trusted proxy list.

🎯

Companion .headers

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 ready

--systemd generates a production unit file with security hardening. PID file, auto-restart, SIGHUP reload.