An open API service indexing awesome lists of open source software.

https://github.com/oxphp/oxphp

Async PHP application server written in Rust. Replaces nginx + PHP-FPM with a single binary.
https://github.com/oxphp/oxphp

application-server async cloud docker http http-server http2 hyper kubernetes php php-fpm-alternative php8 rust sapi tokio zts

Last synced: 1 day ago
JSON representation

Async PHP application server written in Rust. Replaces nginx + PHP-FPM with a single binary.

Awesome Lists containing this project

README

          


OxPHP

Multithreaded PHP application server built for cloud-native infrastructure.


OxPHP is an asynchronous PHP application server written in Rust —

built for production workloads that demand low latency, high concurrency, and zero-config observability.


Docs · RU · 中文 · README RU · README 中文


Quick Start · Why OxPHP · Configuration


Rust
PHP
License
Release
Stars
Docker
HTTP/2
TLS

---

## Quick Start

Two lines. That's it.

```dockerfile
FROM ghcr.io/oxphp/oxphp:0.2.0

COPY --chown=www-data:www-data . /var/www/html
```

> **Note:** By default, `DOCUMENT_ROOT` is `/var/www/html/public`. Place your entry point scripts (e.g. `index.php`) inside the `public/` subdirectory — OxPHP will serve files from there, not from the root of `/var/www/html`. This matches the conventional layout of frameworks like Laravel, Symfony, and Slim out of the box.

```bash
docker build -t my-app . && docker run -p 8080:8080 my-app
curl http://localhost:8080/
```

No nginx config. No PHP-FPM pool tuning. No process manager. Just your app.

---

## Why OxPHP?

OxPHP replaces nginx + PHP-FPM with a single container. The server works out of the box — TLS, Brotli compression, rate limiting, Prometheus metrics, health checks, and structured JSON logs are configured via environment variables.

| | nginx + PHP-FPM | FrankenPHP | RoadRunner | **OxPHP** |
|---|---|---|---|---|
| Language | C / C | Go + C | Go | **Rust** |
| HTTP/2 | ✅ | ✅ | ✅ | ✅ |
| TLS built-in | ✅ | ✅ | ✅ | ✅ (rustls, TLS 1.3) |
| Worker mode | ❌ | ✅ | ✅ | ✅ |
| Backpressure / 529 | manual | ❌ | ❌ | ✅ built-in |
| Prometheus metrics | plugin | plugin | plugin | ✅ built-in |
| Per-IP rate limiting | nginx module | ❌ | ❌ | ✅ built-in |
| Custom error pages | ✅ (nginx config) | ✅ (Caddyfile) | ❌ | ✅ preloaded at startup |
| HTTP/3 | ✅ | ✅ | ✅ experimental | 🔜 roadmap |
| HTTP 103 Early Hints | ✅ (v1.29+) | ✅ | ✅ | 🔜 roadmap |
| Memory safety | ❌ | partial | partial | ✅ Rust |

See the full [documentation](docs/en/index.md) for details.

---

## Benchmarks

> Formal benchmarks are coming soon. We are working on a reproducible test suite covering req/s, latency (p50/p99), memory usage, and worker throughput under concurrent load.

---

## Features

### PHP Runtime
- **Native PHP execution** via custom SAPI (`oxphp`) with ZTS worker pool
- **Full superglobals** support: `$_SERVER`, `$_GET`, `$_POST`, `$_COOKIE`, `$_FILES`, `php://input`
- **HTTP Object API** — `oxphp_http_request()` returns a typed, lazy-loading request object with built-in JSON body parsing, content-detected MIME types for uploads, and a mutable attributes container for middleware; see [HTTP Request API docs](docs/en/php/request-api.md)
- **Native Rust↔PHP bridge** — zero-serialization via direct `zval` access through C accessor functions
- **Plugin system** with typed event dispatch, priority ordering, and PHP function registration
- **Attribute-based decorators** — intercept function/method calls via PHP 8+ attributes with zero overhead on undecorated code; supports `TARGET_FUNCTION`, `TARGET_METHOD`, `TARGET_CLASS`
- **Panic isolation** via `catch_unwind` — a PHP crash does not take down the server

### Worker Model
- **Worker mode** — persistent PHP processes with soft reset, keeping autoloaders and DB connections alive across requests
- **Fiber multiplexing** — each worker handles multiple concurrent requests via PHP 8.4 Fibers; `oxphp_sleep()` and `oxphp_async_await()` yield the fiber instead of blocking the worker
- **Automatic recycling** by request count or memory threshold
- **Worker health monitoring** — dead workers are automatically detected and respawned
- **Early response** via `oxphp_finish_request()` — send the response and keep running background work

### Async Promises
- **`oxphp_async()` / `oxphp_async_await()`** — dispatch closures to a dedicated thread pool for true parallel execution
- **Portable serialization** for `use` variables, arguments, and return values — safe cross-thread binary transfer
- Supported types: scalars, strings, arrays (nested). Resources and objects rejected with `E_WARNING`
- **Exception & die() safety** — exceptions, `die()`, and `exit()` are caught and re-thrown as `OxPHP\AsyncException`
- **Timeout support** — per-task timeouts with `OxPHP\AsyncTimeoutException`
- **`oxphp_async_await_all()` / `oxphp_async_await_any()`** — batch and race primitives

### HTTP & Networking
- **HTTP/1.1 + HTTP/2** auto-detection (h2c) via hyper
- **TLS 1.3** with ALPN (h2 + http/1.1) via rustls
- **3 routing modes** — Traditional, Framework (`index.php`), SPA (`index.html`)
- **SSE streaming** via `Content-Type: text/event-stream` auto-detection or `oxphp_stream_flush()` — cooperative with fiber multiplexing
- **Configurable timeouts** — header read, request, and keep-alive

### Performance
- **LRU file cache** for static files (in-memory ≤1 MB, streaming for larger)
- **HTTP caching** with ETag, Last-Modified, and 304 Not Modified
- **Brotli compression** for text responses (256 B – 3 MB range)
- **mimalloc** allocator for lower allocation latency under contention
- **Configurable Tokio runtime** — multi-threaded by default (CPU/2), tunable via `TOKIO_WORKERS`

### Observability
- **W3C Trace Context** — automatic `traceparent`/`tracestate` propagation, `$_SERVER['OXPHP_TRACE_ID']` for PHP log correlation
- **OpenTelemetry** — OTLP span export (gRPC/HTTP) with semantic conventions, configurable sampling, batch processing
- **APM auto-instrumentation** — 33 internal PHP functions (PDO, mysqli, cURL, Redis, Memcached, file I/O) hooked at the engine level; every call becomes a span with zero code changes
- **`#[OxPHP\Tracing\Trace]` decorator** — annotate any function or method with a PHP 8 attribute to create spans automatically
- **PHP tracing SDK** — 10 `oxphp_trace_*()` functions for manual span creation, attributes, events, error recording, and trace context propagation
- **Prometheus metrics** at `/metrics` — per-worker, zero dependencies
- **Health check** at `/health` — ready for K8s readiness probes
- **Structured error logging** — PHP errors routed through `tracing` with `php_error_type`, `php_file`, `php_line`
- **JSON access logging** with optional `trace_id`/`span_id` fields (levels: `all`, `error`, off via `ACCESS_LOG`)
- **Request ID** generation + pass-through (`X-Request-ID`); trace-derived when OTel enabled

### Reliability & Operations
- **Bounded request queue** with 529 backpressure when full
- **Per-IP rate limiting** with `X-RateLimit-*` headers and 429 responses
- **Custom error pages** — pre-loaded at startup, zero I/O on the hot path
- **Path traversal protection** with symlink escape detection
- **Non-root container** execution as www-data (UID 82)

---

## Architecture

```
┌──────────────┐
│ Tokio async │ configurable: single- or multi-threaded
│ HTTP server │ (hyper + hyper-util + mimalloc)
└──────┬───────┘

┌──────▼───────┐
│Route dispatch│ static file / PHP / 404
└──────┬───────┘
┌────────────┼────────────┐
▼ ▼ ▼
Static file PHP request Not found
(LRU cache) (channel) (404)

┌──────▼───────┐
│Bounded queue │ crossbeam bounded channel
│(backpressure)│ 529 when full
└──────┬───────┘

┌────────────┼────────────┐
▼ ▼ ▼
PHP Worker PHP Worker PHP Worker OS threads (ZTS)
(SAPI exec) (SAPI exec) (SAPI exec) with thread-local state
──────────────────┬──────────────────

┌──────▼───────┐
│ Async pool │ oxphp_async() / oxphp_async_await()
│(crossbeam ch)│ dedicated OS threads (ZTS)
└──────┬───────┘
┌────────────┼────────────┐
▼ ▼ ▼
Async Worker Async Worker Async Worker
```

- **Tokio async runtime** — multi-threaded by default, tunable via `TOKIO_WORKERS`
- **ZTS worker pool** — each worker is a dedicated OS thread with `catch_unwind` isolation
- Workers receive requests via `crossbeam::bounded`, respond via `ExecuteResult` (immediate or deferred via `oneshot`)
- **Async pool** — separate OS threads for `oxphp_async()` tasks, preventing deadlocks with the HTTP pool
- **Worker mode** — persistent PHP with soft reset; keeps bootstrap state (autoloaders, DB connections) alive

### Internal Server

When `INTERNAL_ADDR` is set, a lightweight HTTP server starts on a separate port:

| Endpoint | Description |
|----------|-------------|
| `GET /health` | JSON health status (uptime, requests, connections) |
| `GET /metrics` | Prometheus text format metrics |
| `GET /config` | JSON runtime configuration (TLS paths redacted) |

---

## Configuration

All settings are via environment variables — no config files required.

| Variable | Default | Description |
|---|---|---|
| `LISTEN_ADDR` | `0.0.0.0:8080` | Address and port to bind |
| `DOCUMENT_ROOT` | `/var/www/html/public` | Filesystem path to serve files from |
| `INDEX_FILE` | *(unset)* | Routing mode: empty = Traditional, `index.php` = Framework, `index.html` = SPA |
| `TOKIO_WORKERS` | `0` (CPU / 2, min 1) | Async I/O threads; `0` = auto |
| `EXECUTOR` | `sapi` | PHP executor: `sapi` (real PHP) or `stub` (test mode) |
| `PHP_WORKERS` | `0` (CPU / 2, min 1) | Worker pool: `N` = fixed, `MIN:MAX` = dynamic, `0` = auto |
| `PHP_WORKERS_IDLE_SECONDS` | `30` | Idle timeout before retiring a dynamic worker |
| `QUEUE_CAPACITY` | `PHP_WORKERS * 128` | Bounded channel size; 529 when full |
| `DRAIN_TIMEOUT_SECONDS` | `30` | Graceful shutdown drain timeout |
| `LOG_LEVEL` | `info` | Tracing verbosity: `error`, `warn`, `info`, `debug`, `trace` |
| `INTERNAL_ADDR` | *(unset)* | Internal server for health/metrics/config (e.g. `0.0.0.0:9090`) |
| `RATE_LIMIT` | `0` (off) | Max requests per IP per window |
| `RATE_WINDOW_SECONDS` | `60` | Rate limit window in seconds |
| `HEADER_TIMEOUT_SECONDS` | `5` | Header read timeout (Slowloris protection) |
| `REQUEST_TIMEOUT_SECONDS` | `120` | Overall request timeout; 0 = disabled |
| `TLS_CERT` | *(unset)* | Path to TLS certificate PEM file |
| `TLS_KEY` | *(unset)* | Path to TLS private key PEM file |
| `ERROR_PAGES_DIR` | *(unset)* | Directory with custom error pages (`{status}.html`) |
| `STATIC_CACHE_TTL` | `30d` | Static file cache TTL (`30s`, `5m`, `2h`, `30d`, `1y`, `off`) |
| `STATIC_CACHE` | *(on)* | Set to `off` to enable mtime revalidation on the in-memory content cache |
| `COMPRESSION_LEVEL` | `4` | Brotli quality (0 = off, 1–11) |
| `ACCESS_LOG` | *(off)* | Per-request JSON log: `all`, `error`, or unset |
| `MAX_CONNECTIONS` | `10000` | Maximum concurrent connections |
| `WORKER_FILE` | *(unset)* | Path to worker PHP script; enables persistent worker mode |
| `WORKER_MAX_REQUESTS` | `0` (unlimited) | Max requests per worker before recycling |
| `WORKER_MAX_MEMORY_MIB` | `0` (unlimited) | Max memory (MiB) per worker before recycling |
| `ASYNC_WORKERS` | `0` (disabled) | Dedicated async worker threads for `oxphp_async()` |
| `ASYNC_QUEUE_CAPACITY` | `ASYNC_WORKERS * 64` | Bounded queue for async tasks; rejected when full |
| `SPLIT_PATH_INFO_ENABLED` | `false` | PATH_INFO splitting for `/script.php/extra/path` URIs (legacy CGI compat) |
| `TRACE_CONTEXT` | `false` | W3C Trace Context propagation (`traceparent`/`tracestate`). Auto-enabled when `OTEL_ENABLED=true` |

### OpenTelemetry (`plugin-otel` feature)

| Variable | Default | Description |
|---|---|---|
| `OTEL_ENABLED` | `false` | Enable span export. Implies `TRACE_CONTEXT=true` |
| `OTEL_EXPORTER_OTLP_ENDPOINT` | `http://localhost:4317` | OTLP collector endpoint |
| `OTEL_EXPORTER_OTLP_PROTOCOL` | `grpc` | Export protocol: `grpc` (port 4317) or `http/protobuf` (port 4318) |
| `OTEL_EXPORTER_OTLP_TIMEOUT` | `10000` | Export timeout in milliseconds |
| `OTEL_EXPORTER_OTLP_HEADERS` | *(unset)* | Auth headers for hosted backends (`key=value,key=value`) |
| `OTEL_SERVICE_NAME` | `oxphp` | Service name in exported traces |
| `OTEL_SERVICE_VERSION` | *(unset)* | Service version in exported traces |
| `OTEL_RESOURCE_ATTRIBUTES` | *(unset)* | Resource attributes (`key=value,key=value`) |
| `OTEL_TRACES_SAMPLER` | `parentbased_traceidratio` | Sampler: `always_on`, `always_off`, `traceidratio`, `parentbased_traceidratio` |
| `OTEL_TRACES_SAMPLER_ARG` | `1.0` | Sampling ratio (0.0–1.0) |

### APM (`plugin-apm` feature)

| Variable | Default | Description |
|---|---|---|
| `OTEL_APM_ENABLED` | `false` | Enable APM: auto-instrumentation, error capture, PHP tracing SDK. Requires `OTEL_ENABLED=true` |
| `OTEL_APM_SLOW_QUERY_MS` | `100` | Slow query threshold (ms). Queries above this get `oxphp.db.slow=true` |
| `OTEL_APM_DB_CAPTURE_PARAMS_ENABLED` | `false` | Record bind parameters in `db.params` span attribute |

---

## Build

```bash
# Host (without PHP — all tests pass, no PHP execution)
cargo build --release

# Docker (with PHP — full functionality)
docker compose build
```

### Run locally (static files only)

```bash
DOCUMENT_ROOT=./www/public ./target/release/oxphp
```

## Development

```bash
# Full verification (host)
cargo fmt -- --check && cargo clippy --no-default-features -- -D warnings && cargo test --no-default-features

# Docker smoke tests
docker compose build && docker compose up -d
curl http://localhost:8080/
curl "http://localhost:8080/test_superglobals.php?foo=bar"
curl -X POST -d "key=value" http://localhost:8080/test_superglobals.php
curl -H "Cookie: session=abc" http://localhost:8080/test_superglobals.php

# Async promises
curl http://localhost:8080/test_async.php
curl http://localhost:8080/test_async_parallel.php
curl http://localhost:8080/test_async_die.php

# Internal server
INTERNAL_ADDR=127.0.0.1:9090 ./target/release/oxphp &
curl http://localhost:9090/health
curl http://localhost:9090/metrics
```

---

## Roadmap

> Items are not ordered by priority. Presence on this list does not guarantee implementation.

| Feature | Description |
|---|---|
| **PHP 8.5** | Support for PHP 8.5 |
| ~~**Trace Context (W3C)**~~ | ✅ Implemented — automatic propagation of `traceparent` / `tracestate` headers (W3C spec), enabled via `TRACE_CONTEXT=true` |
| ~~**OpenTelemetry**~~ | ✅ Implemented — OTLP trace export via `plugin-otel` feature, W3C context propagation, per-request spans with standard semantic conventions |
| ~~**APM & Auto-Instrumentation**~~ | ✅ Implemented — `plugin-apm` feature: automatic tracing of 33 internal PHP functions (PDO, mysqli, cURL, Redis, Memcached, file I/O), `#[OxPHP\Tracing\Trace]` decorator, 10 `oxphp_trace_*()` SDK functions, PHP error capture |
| **Custom Metrics** | PHP API for registering application-defined Prometheus metrics from userland code |
| **Built-in PHP Profiler** | Low-overhead profiling via attribute decorators (`#[Timer]`, `#[Span]`), integrated with server metrics and tracing |
| **Dockerfile.bookworm** | Official Debian Bookworm-based image as an alternative to Alpine |
| **Non-Docker Install** | Native installation via system package managers (apt, brew, etc.) |
| **HTTP/3** | QUIC-based HTTP/3 support |
| **HTTP 103 Early Hints** | Send `103 Early Hints` responses to allow clients to preload resources before the final response |
| **Ecosystem Plugins** | Expanded plugin system: more lifecycle hooks, richer PHP API, and documentation for third-party plugin authors |
| ~~**Shared Async Runtime**~~ | ✅ Implemented — Tokio runtime powers `oxphp_async()` / `oxphp_async_await()` with timeouts, result delivery, and race coordination |
| **Database Connection Pool** | Built-in connection pooling via `sqlx`, reducing per-request connection overhead |
| **gRPC Server** | *(speculative)* An alternative server mode — gRPC instead of HTTP; very uncertain, may not happen |
| ~~**Promise API**~~ | ✅ Implemented — `oxphp_async()` / `oxphp_async_await()` with dedicated thread pool, portable serialization, and exception safety |
| ~~**Fiber Multiplexing**~~ | ✅ Implemented — each worker handles multiple concurrent requests via PHP 8.4 Fibers; `oxphp_sleep()` / `oxphp_usleep()` and `oxphp_async_await()` yield the fiber cooperatively |
| **Diagnostics** | Production doctor: checks OS limits (ulimit, TCP backlog, epoll/kqueue, container settings), identifies performance bottlenecks (worker queue depth, lock contention, GC/alloc pressure, ZTS stats), and gives specific actionable recommendations |

## Documentation

- [English](docs/en/)
- [Русский](docs/ru/)
- [中文](docs/zh/)

## License

[AGPL-3.0](LICENSE)