Getting Started

Install, configure, deploy. One file, one command.

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
FlagDefaultDescription
--root=DIR./webDocument root
--app=DIR—Qbix app directory (loads full Q framework)
--host=IP0.0.0.0Bind address
--port=N80HTTP port
--https-port=N443HTTPS port (when certs exist)
--workers=NautoPersistent 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]
            }
        }
    }
}
FieldWhat it does
handlerHandler path — dispatched via Q::event()
everyRun every N seconds from startup
timesRun at specific HH:MM times (24h format)
weekdaysOnly on these days: mon, tue, wed, thu, fri, sat, sun
monthdaysOnly 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
            }
        }
    }
}
SettingDefaultDescription
bufferSize64KBBuffer in memory, flush in a single write() syscall
flushInterval1sMax latency before buffer hits disk
maxSize50MBRotate mid-day if exceeded
archiveAfterDays2Compress to .gz
deleteAfterDays30Delete old archives
Performance impact
ModeThroughputOverhead
No logging9,276 req/s—
Buffered (64KB, 1s)8,671 req/s6.5%
Unbuffered8,186 req/s11.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.