Releases

Changelog

Every notable change to the server, the PHP API and the images — curated highlights per release. The full, unabridged history lives in CHANGELOG.md.

latest v0.10.0 · 2026-07-12 · 10 releases · AGPL-3.0 CHANGELOG.md →
oxphp — releases bash
$ docker pull ghcr.io/oxphp/oxphp:0.10.0
0.10.0: Pulling from oxphp/oxphp
Status: Image is up to date
# opt-in PHP 8.5 preview:
$ docker pull ghcr.io/oxphp/oxphp:php8.5
unreleased
git: main
UNRELEASED
CHANGELOG.md →
ADDED

Auto-captured unhandled exceptions on the root span — A request that fails with an uncaught exception or fatal error and returns 5xx now attaches an OpenTelemetry exception event (type, message, stacktrace, file, line) to the root SERVER span with no #[OxPHP\Apm\Trace] attribute and no oxphp_apm_error() call, so a 500 is self-describing in error backends. Framework apps that render their own error page still record it explicitly.

CHANGED

FRAME_OPTIONS now defaults to SAMEORIGIN, not DENY — Same-origin framing (admin previews, dashboard widgets, same-origin payment components) works out of the box while cross-origin clickjacking stays blocked, matching nginx and Rails; set FRAME_OPTIONS=DENY to forbid all framing.

v0.10.0
2026-07-12
LATEST
⚠ breaking changes
Release notes →
BREAKING

Privilege drop by default — Started as root where a www-data account exists, the server binds its listeners and drops to www-data before serving any traffic; the official image no longer runs as root. Pass --user=root to keep the old behavior.

Fail-closed TLS config — A half-configured pair (TLS_CERT without TLS_KEY, or vice versa) or a non-UTF-8 cert/key value now aborts startup instead of silently serving plain HTTP, naming the offending variable.

Strict ASYNC_* / SUPERGLOBALS parsing — Garbage in ASYNC_WORKERS, ASYNC_QUEUE_CAPACITY or ASYNC_MAX_FIBERS is a startup error instead of a silent default, and oxphp run no longer ignores an invalid SUPERGLOBALS_ENABLED.

QUERY without Content-Type returns 400, not 415 — The only server-generated 415 is gone; a custom ERROR_PAGES_DIR/415.html for this case must be renamed to 400.html.

Profiler export envelope resolution — PROFILER_EXPORT_XHGUI=false now wins even on a /run/import URL, and envelope auto-detection keys only on each tool’s canonical path (xhgui /run/import, Buggregator /api/profiler/store), no longer on a fuzzy "xhgui" substring.

ADDED

Buggregator profiler export — The profiler’s HTTP push wraps xhprof data in the envelope Buggregator’s POST /api/profiler/store expects; PROFILER_EXPORT_BUGGREGATOR toggles it, with tags and app_name set via env.

OTLP trace export over TLS — An https:// OTEL_EXPORTER_OTLP_ENDPOINT is now exported encrypted on both gRPC and http/protobuf, verified against the system trust store via rustls; the runtime image must ship a CA bundle (the official one does).

Full exception data on APM spans — The exception span event now carries exception.message and exception.stacktrace alongside exception.type, so New Relic Errors Inbox, Jaeger and Tempo render the message and stack; capped by OTEL_APM_MESSAGE_MAX_BYTES / OTEL_APM_STACKTRACE_MAX_BYTES.

Graceful drain of long-lived connections — On SIGTERM the server sends GOAWAY, closes idle keep-alives and ends open SSE/flush streams promptly; DRAIN_TIMEOUT_SECONDS default lowered 30 → 25 to fit Kubernetes’ 30s grace period.

TLS_MIN_VERSION — Minimum accepted protocol version: 1.2 (default) or 1.3. Anything else — including a foreign syntax like "TLSv1.2" — is a hard startup error, validated even when TLS is disabled and reported as tls_min_version in /config.

CHANGED

Cheap STATIC_REVALIDATE — stat() at most once every 3 seconds per file; a static hit is served from memory with at most one syscall, and on-disk changes become visible within 3s. Default stays off.

Clearer TLS startup errors — A typo’d path fails with "TLS_CERT: cannot read /etc/ssl/cert.pem: No such file or directory" instead of a bare OS error; PEM-parse and key-mismatch errors are prefixed the same way.

FIXED

Worker-mode cancellation targeting — A client abort or request timeout could cancel whichever request was most recently accepted on the worker instead of the one actually being cancelled, aborting an unrelated in-flight request.

finish_request() vs multiplexed streams — Once any request on a worker called oxphp_finish_request(), every other stream multiplexed onto that worker silently dropped its subsequent oxphp_stream_flush() output.

v0.9.0
2026-06-25
⚠ breaking changes
Release notes →
BREAKING

Timed-out await cancels the task — An abandoned task is force-cancelled: parked tasks resume into cancellation, CPU-bound ones are interrupted at an opcode boundary. Previously it ran to request end.

ADDED

Nested oxphp_async() — An async task can dispatch tasks of its own and suspend on await_all / await_any / await_race without blocking its worker thread. The “no nested async” restriction is gone.

In-flight task cap — ASYNC_MAX_FIBERS (default 256) bounds queued + running tasks; dispatch past the cap rejects immediately with AsyncException, so fan-out cannot deadlock.

Internal server allow-list — INTERNAL_ALLOW_IPS: a CIDR allow-list for /metrics and /config; health endpoints stay reachable for orchestrators.

Profiler sampling excludes — PROFILER_EXCLUDE_PATHS keeps framework self-traffic (/_profiler, /_wdt) out of statistical sampling; explicit triggers still profile.

FIXED

Channel sender wake-up — A fiber send-waiter parked on a full Shared\Channel wakes when recv_blocking frees a slot — sendTimeout no longer trips spuriously under saturation.

Async edge cases — A graceful-shutdown crash; await on a closed promise stalling instead of rejecting; await_all leaving promises running on early bail-out.

v0.8.0
2026-06-13
⚠ breaking changes
Release notes →
BREAKING

Worker routing contract — Static assets from disk, everything else to the worker ENTRY_FILE — arbitrary .php files, directory indexes and the root index.php fallback are no longer resolved per-request.

Honest CGI PATH_INFO — In Framework mode PATH_INFO is set only for an explicit /index.php/extra request; PHP_SELF reports the front controller. Route on REQUEST_URI.

ADDED

Range requests (RFC 9110) — 206 Partial Content for static files: video seeking, resumable downloads, partial PDF loading; If-Range honored with strong validators.

HTTP/2 connection limits — Five env vars bound per-connection resource use; resolved values are logged at startup.

CHANGED

Strong static ETags — "<size>-<mtime>" replaces W/"…" — required by If-Range; compressed representations keep a weak tag.

FIXED

Application headers win — X-Content-Type-Options, X-Frame-Options and CSP set from PHP are no longer overwritten — server values are insert-if-absent fallbacks.

Streaming vs compression — flush()-streamed responses pass through uncompressed instead of being buffered in full by the brotli layer until script end.

SECURITY

HTTP/2 DoS hardening — Rapid Reset (CVE-2023-44487), HPACK bombs and window-stall attacks are bounded; dead connections reclaimed via PING/PONG keepalive.

v0.7.0
2026-06-05
⚠ breaking changes
Release notes →
BREAKING

remote_ip in access logs — The JSON field remote_addr is renamed remote_ip and carries the IP without a port — matching nginx/Apache/Caddy.

ADDED

PHP-style CLI — oxphp run script.php executes a file under CLI semantics with the full engine underneath; #!/usr/bin/env oxphp scripts run directly; -d ini overrides accepted.

--user privilege drop — Bind privileged ports as root, then irreversibly drop to an unprivileged user before any request-handling thread spawns.

Forwarded port honored — Behind TRUSTED_PROXIES, SERVER_PORT honors X-Forwarded-Port; REMOTE_PORT is filled from RFC 7239 for= when the proxy supplies one.

FIXED

Decorator crashes — Two SIGSEGV in the function-decorator path (freed instance cache; dangling zval across resize) are gone; nesting overflow now throws StackOverflowException.

Profiler attributes honor arguments — #[SlowThreshold(ms:)], #[MemoryThreshold(kb:)] and #[Mark(label:)] no longer collapse to shared defaults; APM span events reach OpenTelemetry.

Upload object API wired — Request::file() / files() return UploadedFile objects for every $_FILES shape instead of null.

v0.6.0
2026-05-28
⚠ breaking changes
Release notes →
BREAKING

Trichotomous wait API — Channel and Mutex split every wait into try* / bare verb / *Timeout(int $ms), returning value-typed SendResult / RecvResult instead of mixed|null.

await_any means Promise.any — First fulfilled wins; all-rejected throws AggregateAsyncException. The old “first settled” behavior is now oxphp_async_await_race().

Honest cancel statuses — Timeout → 504, graceful drain → 503 + Retry-After, client disconnect → 499 (log-only). Supervisor kills and userland cancels keep 500.

Renames without shims — Async\Exception → AsyncException, Shared\Exception → SharedException; Worker drops get* prefixes; Counter goes minimal (add/set); Flag mirrors Atomic; Once gains getOrInit()/status().

ADDED

Shared\Atomic + Ordering — A generic int64 atomic: load/store/swap/CAS/fetch* with explicit memory orderings, one-to-one with std::sync::atomic.

Shared\Registry — Name-keyed process-global handles: every worker converges on the same entry; the factory runs at most once.

REMOVED

One timeout source — REQUEST_TIMEOUT_SECONDS and oxphp_request_heartbeat() are gone — max_execution_time and set_time_limit() remain.

FIXED

connection_aborted() in SSE — Returns true after a mid-stream disconnect, so while (!connection_aborted()) loops exit cleanly.

PERFORMANCE

Shared\* hot path — Per-op overhead lands within criterion noise of a raw atomic load; ≈4.7× geomean at 8 threads vs the previous shape.

v0.5.0
2026-05-05
⚠ breaking changes
Release notes →
BREAKING

Strict boolean env parsing — Only on/true/1/yes and off/false/0/no; a typo like ture refuses to start instead of silently defaulting.

ADDED

ENTRY_FILE + WORKER_MODE_ENABLED — One variable selects the routing mode by extension; an explicit toggle enables persistent workers. INDEX_FILE / WORKER_FILE become deprecated aliases.

OxPHP\Server\Worker class — A runtime introspection handle: id, request count, memory, RSS — plus application-driven recycling via scheduleExit().

CHANGED

Static cache renames — STATIC_CACHE_TTL → STATIC_MAX_AGE; STATIC_CACHE → STATIC_REVALIDATE with flipped polarity. Defaults unchanged.

DEPRECATED

WORKER_MAX_REQUESTS — Parsed and ignored with a startup WARN — migrate to WORKER_MAX_MEMORY_MIB or scheduleExit().

v0.4.0
2026-05-02
Release notes →
ADDED

PHP 8.5 images — :php8.5, :php8.5-alpine and patch-pinned tags ship alongside 8.4; latest stays on 8.4 until the soak window closes.

FIXED

Chunked streaming across requests — The bridge’s per-request context leaked between worker requests, dropping chunked Transfer-Encoding after the first flush.

Shared\* inside user fibers — Channel::recv()/send() inside Fiber::start() no longer take the oxphp-fiber suspend path and crash with fiber_await rc=1.

Named arguments for the plugin API — Plugin-defined classes and functions now advertise real parameter names — named-argument calls work.

Shared handles in async closures — Channel/Map/Pool captured by an oxphp_async() closure no longer arrive as null on the receiving thread.

v0.3.0
2026-04-22
⚠ breaking changes
Release notes →
BREAKING

Async namespace — Async classes moved under OxPHP\Async\; the functions now ship in plugin-async.

ADDED

Seven Shared\* primitives — Counter, Flag, Once, Mutex, Channel, Map, Pool — atomic state shared across workers, with registry observability and a deadlock detector.

Per-request profiler — xhprof / speedscope / pprof / collapsed outputs; cookie, header, query and statistical triggers; HTTP push with an xhgui envelope.

APM auto-instrumentation — plugin-apm with a PHP tracing SDK and error capture; the async and APM subsystems moved into Rust plugins.

Production integrations — TRUSTED_PROXIES (RFC 7239), Kubernetes /readyz + /livez, security headers, per-IP rate limiting — all env-configured.

PERFORMANCE

Pool hot path — Uncontested acquire/release ≈0.9 µs in Docker, with per-thread affinity.

v0.2.0
2026-03-27
Release notes →
ADDED

Fiber multiplexing — Concurrent I/O within a single worker thread; distributed tracing with W3C Trace Context and OpenTelemetry export.

oxphp_async() promises — Parallel PHP execution on a dedicated thread pool.

HTTP object API — OxPHP\Http\Request with lazy accessors, plus the attribute-based decorator system.

CHANGED

Port and backpressure — Default port 8080 → 80 (443 with TLS); the backpressure response 503 → 529.

FIXED

CGI correctness — SERVER_PROTOCOL reflects the real HTTP version; REQUEST_TIME is request start; IPv6 Host parsing; 408 on request timeout.

v0.1.0
2026-03-08
Release notes →
ADDED

Async HTTP core — A Hyper + Tokio server with graceful shutdown, a custom oxphp SAPI with full superglobals, a ZTS worker pool with a bounded queue and 529 backpressure.

Three routing modes — Traditional, Framework and SPA; static files with an in-memory cache, ETag/304, brotli, TLS.

Worker mode — Persistent workers via oxphp_worker(), early response completion, SSE streaming, cooperative timeouts.

Observability — /metrics, /health, /config, structured JSON access logs, request IDs.

Docker-first — A multi-stage Alpine build; amd64/arm64 images on GHCR.