Skip to content

Repository files navigation

Askr — the real server for Laravel & PHP

CI  ·  v1.2.0  ·  MIT

A standalone, memory-safe PHP application server, in Rust.

Askr embeds the PHP interpreter in-process (no FastCGI, no FPM), serves it from a memory-safe Rust hot path, and — in worker mode — boots your app once and serves many requests against it, eliminating per-request framework bootstrap. It is a complete single binary: TLS, HTTP/2, static files, worker supervision and an admin dashboard, with no proxy required in front.

It's the server engine behind the grove ecosystem; Grove stays the local dev tool, Askr is the production server.

Headline result

Real Laravel 12 + Livewire, served entirely in-process:

per-request (the FPM model) worker mode (boot once)
latency / request ~110 ms ~9 ms
throughput (8 workers) 37 req/s 347 req/s

~9×, verified correct under load (300/300 200, each worker booted exactly once, zero state bleed). Raw embedding overhead is ~0.02 ms/request (~56k req/s single-core for a trivial script) — the framework bootstrap is the cost, and worker mode removes it.

Install

Grab a self-contained release for Linux (x86_64 or arm64) — the binary, embedded PHP, opcache, and examples in one tarball, nothing else to install:

VER=v1.2.0; ARCH=$(uname -m)
curl -fsSLO https://github.com/kwhorne/askr/releases/download/$VER/askr-${VER#v}-linux-$ARCH.tar.gz
tar xzf askr-${VER#v}-linux-$ARCH.tar.gz && cd askr-${VER#v}-linux-$ARCH

./askr-run.sh doctor
ASKR_APP_BASE=/var/www/app ./askr-run.sh serve \
  --root /var/www/app/public \
  --worker-script examples/laravel-worker.php \
  --workers "$(nproc)" --tls-self-signed --admin 127.0.0.1:9000

Runtime libs (usually already present): sudo apt-get install -y libssl3 libxml2 libonig5 libsqlite3-0.

Production setup (systemd, TLS, hardening, recommended settings): docs/UBUNTU.md. Building from source: docs/BUILDING.md.

Documentation

Everything lives in docs/:

  • Laravel setuprecommended Laravel guide: composer require kwhorne/askr-laravel, .env, queue/scheduler/broadcasting, durable L2
  • Ubuntu setuprecommended production install (systemd, TLS, tuning)
  • Architecture — how it works, and why processes not threads
  • Buildinglibphp + askr, the extension matrix
  • Configurationaskr.toml, env vars
  • CLI reference — every command and flag
  • Hosting multiple domains — virtual hosts, redirects, multi-domain TLS
  • Stability & compatibility — the 1.0 contract + deprecation policy
  • Worker mode — boot-once-serve-many, state reset, custom workers
  • Docker — one container replaces app+nginx+redis+queue+cron (GHCR, multi-arch)
  • Power features — response cache + tags, coalescing, Pusher WS, defer, autoscaling, record/replay, test runner
  • Shared cacheaskr_cache_* + Laravel driver (no Redis)
  • Broadcasting — live updates via SSE (no Reverb/Pusher)
  • CoW template — boot once, fork workers for ~ms warm respawn (experimental)
  • Admin dashboard — status/reload/metrics API and web UI
  • Auto-TLS (ACME) — obtain + renew Let's Encrypt certs (--acme)
  • Hardening / sandbox — seccomp + Landlock (--sandbox)
  • Benchmarks — vs FrankenPHP, FPM+nginx, RoadRunner (reproducible)
  • Deployment — systemd, TLS, zero-downtime reload, scaling

What works today (1.2.0)

  • Embedded PHP 8.5 (non-ZTS, OPcache + JIT built in) running real Laravel 13 — no FastCGI, no FPM
  • All of Laravel's required extensions + more (intl, gd, curl, zip, pdo_mysql/pgsql, …) — runs Filament apps
  • File uploads stream to temp files (constant memory) with $request->file() in worker mode
  • Response compression (br/gzip by Accept-Encoding), JSON access log, and Prometheus /metrics
  • Multi-core: one worker process per core on a shared listen socket
  • Worker mode (Octane-style) with per-request state reset — no bleed
  • Response cache with tag invalidation (--response-cache): cacheable pages served from Rust at static-file speed; askr_cache_forget_tag() invalidates across all workers instantly — a Varnish-effect, app-driven, zero infra
  • ESI fragment assembly: cache a page with holes and fill them per request, so one live widget stops making a whole page uncacheable — each hole with its own TTL, tags and PURGE
  • PURGE/BAN over HTTP and declarative per-path [[cache.rule]] — cache policy for apps you can't edit
  • stale-if-error + saint mode: keep serving held content when PHP or the database falls over, with the real failure still logged and recorded
  • The cache survives restarts — saved on graceful shutdown, restored at boot, so a restart doesn't pay a cold-cache stampede
  • Rate limiting ([[ratelimit]]) enforced across the whole worker fleet from shared memory, before PHP is woken — no Redis needed for it
  • Canary deploys that stop themselves: the new worker is compared against the rest of the fleet, and a failed canary is drained instead of serving a bad build
  • askr tune — measures your app and prints the config it should have
  • Request coalescing: identical cold-cache requests run PHP once (no stampede)
  • Pusher-compatible WebSocket (--pusher): drop-in Reverb — Echo works with no frontend change (WS /app/{key} + trigger POST /apps/{id}/events)
  • askr_defer(): run work after the response is sent (email/webhooks, no queue)
  • Elastic autoscaling (CoW, --workers-min/--workers-max): warm workers added under load and harvested when idle — practical only because respawn is ~ms
  • Record & replay (--record-errors): a 5xx is replayable with askr replay
  • Fork-based test runner (askr test): boot once, warm isolated process per file
  • --paranoid state-bleed detector: tells you if your app is worker-safe
  • CoW template (--cow): boot once, fork workers — ~ms warm respawn + shared memory
  • Queue workers + scheduler supervised in the same binary (no Horizon/cron)
  • Shared cache (askr_cache_* + Laravel driver): cache, counters, atomic locks (Cache::lock) and sessions (large region) — no Redis
  • askr-laravel package: drop-in askr session (shared-memory, no heap leak, ~11–15k req/s flat), cache and queue drivers via composer require
  • Broadcasting: live updates to browsers via SSE + askr_broadcast() — no Reverb/Pusher
  • Graceful worker recycling (--max-requests) + auto-respawn + crash resilience
  • TLS (rustls, ring) + HTTP/2 (ALPN); --tls-self-signed for dev, or auto-TLS via ACME/Let's Encrypt (--acme)
  • Zero-downtime rolling reload on SIGHUP — with optional canary (bad deploys hit one worker, not all)
  • Request hardening: body-size limit (413), HEAD, GET/POST
  • Typed askr.toml config + config-check
  • Built-in admin dashboard + API (status, graceful reload, live metrics)
  • In-process metrics: PHP-vs-I/O time split, latency histogram, per-worker RSS
  • askr doctor pre-flight checks, and askr upgrade (verified self-update: sha256 + atomic swap)
  • Hardening (--sandbox, Linux): seccomp no-exec + Landlock write-restriction
  • Memory-safe: all unsafe confined to the PHP FFI boundary

Roadmap

Phase Status
M0 — embedding spike (PHP in-process from Rust)
A1 — standalone askr serve over HTTP
A3 — multi-core (fork per core, shared listener)
A4 — worker mode: real Laravel, zero per-request bootstrap
A5 — recycling, state reset, TLS+HTTP/2, rolling reload, doctor
A2 — request hardening (body limit, HEAD, POST)
A6 — typed config + admin dashboard/API
0.2.0 — paranoid, shared cache, SSE broadcast, queue+scheduler, metrics, canary reload, CoW template
self-contained Linux releases (x86_64 + arm64)
0.2.1 — static caching/streaming/Range, slowloris timeouts, pinned & cached CI
0.3.0 — response cache + tag invalidation, coalescing, Pusher WS, askr_defer, CoW autoscaling, record/replay, fork test runner
0.3.1 — Pusher private/presence auth (HMAC subscription verification)
0.3.2 — io_uring groundwork: doctor probe, benchmark harness, design plan
0.4.0 — multipart file uploads (streamed to temp files, $_FILES in worker mode)
0.4.1 — response compression (br/gzip), JSON access log, Prometheus /metrics, KV cache eviction
0.4.2 — Docker image (GHCR, multi-arch), cgroup-aware worker default
0.5.0 — full extension set (intl/gd/curl/zip/pdo_mysql/pgsql) — runs Filament
0.5.1 — fix: empty static files (Vite CSS-only entry) served with correct Content-Length
0.5.2 — supervised external sidecars (Inertia SSR: node bootstrap/ssr/ssr.mjs)
0.6.0 — cache size classes (64 KB values), atomic add/Cache::lock, sessions — Redis-free
0.6.1 — shared-memory job queue (askr_queue_* + AskrQueue driver): delayed jobs, retries
0.7.0 — auto-TLS via ACME/Let's Encrypt (--acme, HTTP-01) — single binary, no proxy
0.8.0 — hardening: seccomp no-exec + Landlock filesystem sandbox (--sandbox)
0.8.2 — PHP 8.5 + Laravel 13, OPcache/JIT built in
Benchmarks: CoW ~1.6× FrankenPHP, ~3× FPM on server overhead (validated) (details)
0.8.3 — fix: worker mode respawns instead of 502-flooding on OOM under load
askr-laravel composer package: shared-memory session/cache/queue drivers
0.8.4 — security/robustness: httpoxy filter, PID-aware shm lock, fork-safe admin start, upload temp-file RAII cleanup
0.9.0 — stale-while-revalidate, leak-aware recycling (--max-rss), traffic shadowing (--shadow-to), compress-cache-once, atomic askr_cache_touch
0.9.1 — backlog-driven queue-worker autoscaling (--queue-max, Horizon balance=auto with no extra daemon) + queue metrics
0.9.2 — optional durable L2 queue backend over SQL Anywhere (--features sql-backend, ASKR_QUEUE_DB); default build unchanged
0.9.3 — L2 cache + pub/sub backends, L1→L2 write-through, L2 queue autoscaling, Laravel broadcasting driver (BROADCAST_CONNECTION=askr) — completes the Redis-free Laravel surface; opt-in
0.9.4 — observability sink: ship per-request logs to ElyraSQL / any MySQL-wire DB (--features observ, ASKR_OBSERV_DSN); thorough Laravel setup guide
0.9.5-full tarball + Docker image (:full) with the L2 SQL Anywhere backends + observability compiled in — no source build needed
0.9.6 — HTTP/3 (QUIC, --http3), OpenTelemetry traces (--features otel), finer response.build span, metrics-rollup table
0.9.7 — streaming HTTP/3 responses (SSE + large files over QUIC), finer + fast-path traces, 1.0 compatibility contract (STABILITY.md), honest h3-under-loss benchmark
0.9.8 — robustness pass: shared-memory cache tombstone deletion, eviction race, empty-upload UPLOAD_ERR_NO_FILE, worker buffer reclaim, configurable slowloris timeouts
0.9.9 — perf follow-through: per-thread request-field arena (no per-field CString), gentler shmlock backoff, channel-sharded SSE hub, askr_cache_oversize_total metric
0.9.10 — review pass: relaxed shared-memory pointer ordering (Acquire/Release), 0700 upload temp dir, graceful flush window before worker exit(75)
0.9.11 — admin bearer-token auth (ASKR_ADMIN_TOKEN), dropped unmaintained rustls-pemfile, extracted supervisor.rs from main.rs, concurrent cache stress test
0.9.12 — multi-domain hosting: virtual hosts ([[site]]) + redirects (www→apex, force_https); streaming PHP output on flush(); boot crash-loop guard; TLS cert hot-reload; wider cache probe
1.0.0 — 🎉 stable release: the surface is frozen under SemVer (STABILITY.md); stress-validated at tens of millions of requests, 100% success
1.0.1 — security: static serving never discloses PHP sources or dotfiles (.env); stale-if-error + saint mode; smart cache-key normalisation
1.1.0 — Varnish-grade cache: ESI fragment assembly, HTTP PURGE/BAN, declarative per-path [[cache.rule]] — all in-process
1.2.0 — operations: fleet-wide rate limiting before PHP, canary judged against the fleet + failed canary drained, cache survives restarts, askr tune
Post-1.0 — durable-tier polish, per-site worker pools, and the experiments tracked in the issue tracker (AI/LLM cache, Varnish-grade edge cache, P2P cluster). 🔭

1.0 is a frozen, stress-validated base. The benchmark against FrankenPHP/FPM/RoadRunner is done (BENCHMARKS.md) and, together with HTTP/3-under-loss numbers, is what shaped the direction; io_uring was deprioritised by that data (PHP is ~99.5 % of request time, I/O ~0.5 % — see docs/IO-URING.md). Post-1.0 work continues under the SemVer + deprecation guarantees in STABILITY.md.

Project layout

crates/
  askr/          the standalone server binary
  askr-php/      embeds PHP (embed SAPI) via FFI
scripts/
  build-libphp.sh   reproducible libphp build (minimal | laravel)
examples/
  laravel-worker.php   worker-mode template for Laravel
  askr.toml            example configuration
docs/              full documentation

Contributing

Contributions are welcome. See CONTRIBUTING.md for how to build (you need an embed libphp first) and the checks CI runs. Please follow the Code of Conduct, and report security issues privately per the Security Policy — not in public issues.

License

MIT © Wirelabs AS — see LICENSE.

About

A standalone, memory-safe PHP application server in Rust. Embeds PHP in-process (no FastCGI, no FPM), boots your app once and serves many — ~9× the FPM model on real Laravel. TLS, HTTP/2, multi-core, zero-downtime reload.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Releases

Sponsor this project

Packages

Contributors

Languages