TrueAsync Server

(PHP 8.6+, true_async_server 0.6+)

TrueAsync Server is a native PHP extension that runs a high-performance HTTP server directly inside the PHP process. No separate daemon, no reverse-proxy, no FastCGI bridge.

Out of the box it supports HTTP/1.1 and HTTP/2 on the same TCP port. Protocol selection happens via ALPN negotiation (for TLS) or HTTP Upgrade. HTTP/3 runs on the same UDP port (QUIC) and is advertised to clients through the Alt-Svc header.

WebSocket, SSE, and gRPC are already designed around the same single-listener-with-protocol-detect model, but are still in progress (see Roadmap).

php
use TrueAsync\HttpServer;
use TrueAsync\HttpServerConfig;

$server = new HttpServer(
    (new HttpServerConfig())
        ->addListener('0.0.0.0', 8080)
        ->setWorkers(4)
);

$server->addHttpHandler(function ($request, $response) {
    $response->setStatusCode(200)->setBody('Hello, World!');
});

$server->start();

Why

The goal of the server is to unlock the potential of concurrent PHP applications.

TrueAsync gave the language real coroutines, non-blocking I/O, and connection pools. To turn that potential into production throughput, you need a server that is designed for the model from the start: a long-running process with an event loop, where every request gets its own coroutine and the scheduler switches between them on every I/O wait.

TrueAsync Server is that server. There is no layer between coroutines and the network: listener, protocol parser, request dispatcher, and handler all live in one process and one event loop. Database connections are reused through Async\Pool, opcache stays hot between requests, and the cold-start cost is paid once, at start().

Features

StatusFeatureDetails
HTTP/1.1Full RFC 9112 compliance, keep-alive, pipelining (via llhttp — the same parser Node.js uses)
HTTP/2Multiplexing, server push (libnghttp2 ≥ 1.57, floor for CVE-2023-44487)
HTTP/3 / QUICUDP transport on libngtcp2 + libnghttp3, OpenSSL 3.5 QUIC TLS API
TLS 1.2 / 1.3OpenSSL 3.x, ALPN negotiation, weak ciphers disabled
Compressiongzip (zlib-ng / zlib), Brotli, zstd: for responses and inbound body decoding across all protocols
Multipart / file uploadsStreaming zero-copy parser
BackpressureCoDel (RFC 8289), adaptive accept pause under load
Streaming request bodyOptional via HttpRequest::readBody(); uploads without keeping the body in RAM
sendFileEfficient file delivery from disk directly out of the handler
Built-in worker poolsetWorkers(N): N threads via Async\ThreadPool + SO_REUSEPORT
Per-request scopeEach handler in its own scope; Async\request_context() gives a shared context across the entire request coroutine tree
Native coroutinesDeep TrueAsync integration: any blocking I/O in the handler suspends the coroutine, not the thread
Zero-copyMinimal allocations on the hot path
📋WebSocketRFC 6455, Upgrade from HTTP/1.1 and HTTP/2
📋SSEServer-Sent Events
📋gRPCover HTTP/2, unary and streaming

Architecture: single-threaded event loop

The same model used by NGINX, Envoy, Node.js, and Rust Tokio/hyper.

One thread owns both the connection and the request from accept to send. There is no handoff between an accept thread and a worker thread, no locks, no context switches between them. A single event loop accepts the connection, reads bytes from the socket, parses HTTP, dispatches the request to the handler, and writes the response — without leaving the thread.

       ┌─────────────────────────────────────────┐
       │              Event Loop Thread          │
       │                                         │
accept ─►  parse  ─►  dispatch  ─►  respond      │
       │     ▲                        │          │
       │     └──── coroutine yield ◄──┘          │
       └─────────────────────────────────────────┘

Non-blocking I/O is provided by the libuv reactor (through TrueAsync). When a coroutine needs to wait on a file, database, or the next WebSocket frame, it yields control to the event loop, which immediately picks up the next ready event. The thread never sits idle in read()/recv().

To scale across cores, multi-worker mode is enabled via setWorkers(N): the built-in Async\ThreadPool spins up N OS threads, each with its own independent event loop, and SO_REUSEPORT (Linux/BSD) lets the kernel distribute incoming connections across them. No shared state, no global locks.

Where to start

API reference

Alternatives

FrankenPHP is a separate embeddable server built on Caddy/Go, where PHP acts as a worker. It is a good fit when you need Caddy features (automatic Let's Encrypt, configuration through a Caddyfile) or integration into an existing Caddy deployment. TrueAsync Server is the native alternative without a Go runtime: the server lives directly inside the PHP process.