Install
git clone https://github.com/Qbix/webserver cd webserver php qbixserver.php
Workers auto-detect from your CPU and RAM: min(available_RAM / 200KB, nproc × 200). Override with --workers=N.
Self-contained binary (no PHP install needed)
curl -LO https://github.com/Qbix/webserver/releases/latest/download/qbixserver-linux-x86_64 chmod +x qbixserver-linux-x86_64 ./qbixserver-linux-x86_64 --root=web
Available for Linux x86_64, Linux aarch64, and macOS arm64. The binary bundles PHP 8.3 — nothing else to install.
CLI Options
Full option list
| Flag | Default | Description |
|---|---|---|
--root=DIR | ./web | Document root |
--app=DIR | — | Qbix app directory (loads full Q framework) |
--host=IP | 0.0.0.0 | Bind address |
--port=N | 80 | HTTP port |
--https-port=N | 443 | HTTPS port (when certs exist) |
--workers=N | auto | Persistent workers (auto = nproc × 200 capped by RAM) |
--config=FILE | — | JSON config file |
--preset=NAME | — | Framework preset: laravel, symfony, wordpress, drupal |
--pid=FILE | — | Write PID file |
JSON Configuration
{
"Q": {
"webserver": {
"port": 8080,
"forkPerRequest": false,
"skipSourceCodeTransform": false,
"fallback": "index.html",
"log": { "dir": "logs", "access": true, "error": true }
}
}
}
TLS / HTTPS
HTTPS auto-starts when the server finds certificates. Place them in local/certs/ or configure paths:
Certificate setup
local/certs/ fullchain.pem privkey.pem
Or in config:
{
"Q": {
"web": {
"https": {
"port": 443,
"cert": "/etc/letsencrypt/live/example.com/fullchain.pem",
"key": "/etc/letsencrypt/live/example.com/privkey.pem"
}
}
}
}
Works with Let’s Encrypt, self-signed, or any PEM certificate. The server generates a self-signed cert on first run for inter-server identity — the SHA-256 fingerprint becomes the server’s identity (like SSH known_hosts).
Scheduler (Cron)
Built-in task scheduler — no system crontab, no supervisor. Tasks are handler files dispatched via Q::event().
Configuration
{
"Q": {
"scheduler": {
"cleanup": {
"handler": "tasks/cleanup",
"every": 3600
},
"daily-report": {
"handler": "tasks/report",
"times": ["09:00"]
},
"business-check": {
"handler": "tasks/check",
"times": ["09:00", "12:00", "17:00"],
"weekdays": ["mon", "wed", "fri"]
},
"monthly-invoice": {
"handler": "tasks/invoice",
"times": ["00:00"],
"monthdays": [1]
}
}
}
}
| Field | What it does |
|---|---|
handler | Handler path — dispatched via Q::event() |
every | Run every N seconds from startup |
times | Run at specific HH:MM times (24h format) |
weekdays | Only on these days: mon, tue, wed, thu, fri, sat, sun |
monthdays | Only on these days of the month: [1], [1, 15], etc. |
Handler example
// handlers/tasks/cleanup.php
function tasks_cleanup(&$params, &$result) {
MyApp\Sessions::expireOld();
MyApp\Logs::rotate();
}
On restart, tasks scheduled for the current minute are skipped to avoid double-firing. Interval tasks wait one full interval before their first run.
Logging
Access and error logs with buffered writes, daily rotation, gzip archiving, and retention management.
Configuration
{
"Q": {
"webserver": {
"log": {
"dir": "logs",
"access": true,
"error": true,
"bufferSize": 65536,
"flushInterval": 1,
"maxSize": 52428800,
"archiveAfterDays": 2,
"deleteAfterDays": 30
}
}
}
}
| Setting | Default | Description |
|---|---|---|
bufferSize | 64KB | Buffer in memory, flush in a single write() syscall |
flushInterval | 1s | Max latency before buffer hits disk |
maxSize | 50MB | Rotate mid-day if exceeded |
archiveAfterDays | 2 | Compress to .gz |
deleteAfterDays | 30 | Delete old archives |
Performance impact
| Mode | Throughput | Overhead |
|---|---|---|
| No logging | 9,276 req/s | — |
| Buffered (64KB, 1s) | 8,671 req/s | 6.5% |
| Unbuffered | 8,186 req/s | 11.7% |
Logs rotate daily at midnight. Yesterday’s becomes access.2026-08-11.log. After 2 days, gzipped. After 30 days, deleted. Mid-day rotation if a log exceeds 50MB.
Access log format (nginx-compatible combined + response time):
192.168.1.1 - - [11/Aug/2026:14:30:00 +0000] "GET /api/users HTTP/1.1" 200 1234 "-" "Mozilla/5.0" 3.2ms
Requirements
PHP 8.1+ with sockets, pcntl, and openssl extensions. That’s it. No nginx, no Apache, no Docker, no Composer required.