FrankenPHP 1.13.0 was published on 4 October at 14:01. It updates the bundled Caddy server from 2.11.4, used in FrankenPHP 1.12.7, to 2.11.7. Mercure 1.0 is now embedded. The release adds PHP 8.6 compatibility and upgrades the Go toolchain to 1.27.
Five security issues are fixed. Windows SCRIPT_FILENAME resolution can no longer escape the document root, covered by GHSA-868c-7h7m-mmj9. Caddy 2.11.7 drops dot-form headers by default, addressing header spoofing related to PHP's mapping of Foo.Bar to HTTP_FOO_BAR, covered by GHSA-qcrp-8483-f2f2. CGI path splitting now requires a path-segment boundary, so /uploads/a.php.txt/b.php cannot execute uploads/a.php, covered by GHSA-xxjp-cjxr-2x6m. putenv() no longer writes to the process-wide operating-system environment, preventing values from crossing requests and threads, covered by GHSA-996f-w38m-f574. frankenphp_log() no longer crashes the whole server when it receives a crafted array, covered by GHSA-4prg-hv4r-g6mv. Two of the five issues have a high rating.
Caddy 2.11.7 adds Slowloris protection. Idle read and write timers reset after successful I/O, while stalled connections are terminated. Routes can tune these values with the timeouts directive. The new url_pattern matcher follows the URLPattern web standard and supports named groups, wildcards, and regular expressions. Each captured group is exposed as a placeholder value.
The Incremental: ?1 response header follows RFC 10036 and provides a standard counterpart to NGINX's X-Accel-Buffering. reverse_proxy and encode stream such responses immediately. Server-sent events behind encode are also sent without buffering, improving real-time delivery for Mercure. Graceful shutdown waits for servers from previous configurations so long-lived responses survive a reload. The tls_automate_names global option manages certificates for names outside site blocks.
TLS certificate lookup now runs roughly twice as fast, cutting allocations from 15 to 10. Reverse proxy handling now flushes partial responses correctly, propagates TCP half-close on upgraded streams, and isolates active health checks by check configuration. The expected_underscore_headers and expected_dot_headers options allow selected headers that Caddy drops by default.
Mercure 1.0 changes mercure_publish(). Invalid updates raise ValueError when they use a topic in the reserved /.well-known/mercure namespace, an ID beginning with #, control characters, invalid UTF-8, a negative retry value, or no topic. A failed dispatch raises RuntimeException with the hub's message. Publisher and subscriber keys can be tied to a trusted issuer through the new issuer block. The sample Caddyfile and php-server --mercure use this configuration.
Configuration validation now runs before a replacement becomes active. A missing worker file, duplicate worker name, or inconsistent thread budget leaves the current configuration serving. Library users can call frankenphp.Validate().
The thread model has changed. num_threads now counts threads available for requests that no worker serves, with worker threads added on top. Configurations that set num_threads alongside workers therefore start more threads than before. Startup fails when max_threads is below num_threads plus the worker count, and the error reports all three values. Defaults are unchanged.
Mercure configurations that set publisher_jwt or subscriber_jwt without protocol_version_compatibility now fail validation. The issuer block is required for binding keys to a trusted issuer. During hot reload, $_SERVER['FRANKENPHP_HOT_RELOAD'] now exposes /.well-known/mercure?match=<topic>, so hardcoded URLs using ?topic= must be updated.
Caddy limits request headers to 16 KiB by default. max_header_size can raise the limit. Stalled reads and writes are aborted after one minute. Headers containing dots are filtered like headers containing underscores. A wildcard site's client_auth no longer applies to more specific sites, and additional invalid configurations are rejected. Builds of PHP without --disable-zend-signals, meaning ZTS builds, now refuse to start with an explicit error. This prevents a hang accompanied by growing memory use.
Go-based PHP extensions receive Thread(index), PHPThread.Pin(), PHPThread.IsRequestDone(), and the frankenphp_thread_index() C function. php_server blocks are mirrored on the FrankenPHP side so requests and workers stay within their block. phpinfo() gains a FrankenPHP section with the Caddy version, and Go code can add rows through frankenphp.AddPHPInfoEntry(). On PHP 8.5 and newer, php-cli uses PHP's built-in CLI SAPI. Opcache shared-memory restarts are logged and counted by the experimental frankenphp_opcache_restarts{reason} metric on PHP 8.4 and newer. Such restarts are unsafe under ZTS, where the counter should remain zero. Startup logs now report total_threads and worker_threads.
Requests arriving before regular threads are ready no longer hang, removing random 499 and 504 responses after container startup. Worker crashes are logged at warning level with their exit status. Shutdown() no longer waits forever for a worker that abandoned boot after max_consecutive_failures. Static builds prevent the bundled parallel extension from converting recoverable Go faults into process crashes. The extensions array retained by register_extensions is protected from a possible use-after-free. extgen now handles grouped and nullable parameters, callables, mixed return values, method wrappers, constants, and integer literals in generated extensions.
The documentation records runtime engine settings that persist between requests in worker mode, including ini_set(), stream_context_set_default(), date_default_timezone_set(), and chdir(). It also documents HTTP request filtering differences and expands the metrics documentation. A Yii 3 page was added. The hot reload snippet received a corrected Idiomorph CDN URL, and Turkish translations were completed.




Comments
No comments yet — be the first.
Open the discussion
No account or password needed — just enter your e-mail and we’ll send you a one-time sign-in link. First time here? You’re set up automatically.
Your rating will be applied automatically after you sign in.
Check your inbox
We’ve sent a sign-in link to …. Open it on this device — this tab will sign you in automatically.
Nothing arrived? Check your spam folder — and mark the mail as "Not spam" so it lands in your inbox next time.