FrankenPHP 1.13.0 was released on 4 October at 14:01. Following v1.12.7, it embeds Caddy 2.11.7 and Mercure 1.0. The release adds PHP 8.6 compatibility and upgrades to Go 1.27.
Five security issues are fixed. On Windows, `SCRIPT_FILENAME` resolution can no longer escape the document root, covered by GHSA-868c-7h7m-mmj9. Caddy 2.11.7 drops dot-form header names by default, closing a spoofing path where PHP maps `Foo.Bar` to `HTTP_FOO_BAR` like `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 changes the process-wide OS environment, preventing values from leaking across requests and threads, covered by GHSA-996f-w38m-f574. A crafted array passed to `frankenphp_log()` can no longer crash the complete server process, covered by GHSA-4prg-hv4r-g6mv.
Caddy 2.11.7 includes the changes from Caddy 2.11.6 and 2.11.7. Slowloris protection resets idle read and write timeouts after successful I/O, while the `timeouts` directive allows per-route tuning. The `url_pattern` matcher follows the URLPattern web standard, supports named groups, wildcards, and regular expressions, and exposes captured groups as placeholders.
The `Incremental: ?1` header from RFC 10036 tells `reverse_proxy` and `encode` to stream responses immediately. Server-sent events behind `encode` are no longer buffered, which benefits Mercure. Graceful shutdown waits for servers from earlier configurations, so long-lived responses survive a reload.
The global `tls_automate_names` option can manage certificates for names that have no site block. Certificate lookup during TLS handshakes is about twice as fast and now uses 10 allocations, down from 15. The reverse proxy flushes partial responses correctly and propagates TCP half-close on upgraded streams. Active health checks are isolated for each check configuration. `expected_underscore_headers` and `expected_dot_headers` allow selected headers that Caddy would otherwise drop.
Mercure 1.0 changes `mercure_publish()`. It throws a `ValueError` for an update using 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 a `RuntimeException` containing the hub message. The new `issuer` block binds publisher and subscriber keys to a trusted issuer. The sample Caddyfile and `php-server --mercure` use this configuration.
There are several upgrade changes. `num_threads` now counts threads available for requests that no worker serves, with worker threads added on top. Configurations with workers can therefore start more threads than before. Startup fails when `max_threads` is lower than `num_threads` plus the worker threads, and the error reports all three values. Defaults are unchanged.
Setting `publisher_jwt` or `subscriber_jwt` without `protocol_version_compatibility` is now invalid. Mercure configurations should use the new `issuer` block. During hot reload, `$_SERVER['FRANKENPHP_HOT_RELOAD']` now advertises `/.well-known/mercure?match=<topic>`, so hardcoded URLs using `?topic=` need updating.
Caddy limits request headers to 16 KiB by default, with `max_header_size` available for larger values. Stalled reads and writes are aborted after one minute. Headers containing dots are dropped like headers containing underscores. A wildcard site's `client_auth` no longer applies to more specific sites, and additional invalid configurations are rejected. A PHP build without `--disable-zend-signals` (ZTS) now makes FrankenPHP stop with an explicit error instead of hanging while memory grows.
The release exposes a thread API for Go-based PHP extensions. It includes `Thread(index)`, `PHPThread.Pin()`, `PHPThread.IsRequestDone()`, and the C function `frankenphp_thread_index()`. Configuration reloads are validated before replacing the active configuration. Missing worker files, duplicate worker names, and inconsistent thread budgets are rejected while the current configuration keeps serving. Library users can call `frankenphp.Validate()`.
Startup logs now report `total_threads` and `worker_threads`. Opcache shared-memory restarts are logged and counted by the experimental `frankenphp_opcache_restarts{reason}` metric on PHP 8.4 and later. Such restarts are unsafe under ZTS, so the counter should remain zero. `php_server` blocks are mirrored on the FrankenPHP side, and `phpinfo()` gains a FrankenPHP section with the Caddy version. Go code can add rows through `frankenphp.AddPHPInfoEntry()`.
On PHP 8.5 and later, `php-cli` uses PHP's built-in CLI SAPI. The release also adds the PHP 8.6 compatibility work described above.
Requests arriving before regular threads are ready no longer hang, avoiding random 499 and 504 responses after container startup. Worker crashes are logged at warning level with their exit status, while clean restarts remain at debug level. `Shutdown()` no longer waits forever for a worker that gave up during boot after reaching `max_consecutive_failures`.
Static builds no longer let the bundled parallel extension turn recoverable Go faults into process crashes. The release fixes a possible use-after-free involving the extensions array retained by `register_extensions`. `extgen` now handles grouped and nullable parameters, callables, mixed return values, method wrappers, constants, and integer literals in generated extensions.
The documentation now lists runtime engine settings that persist between requests in worker mode, including `ini_set()`, `stream_context_set_default()`, `date_default_timezone_set()`, and `chdir()`. It documents HTTP request-filtering differences, expands the metrics guide, adds a Yii 3 page, repairs the Idiomorph CDN URL in the hot-reload snippet, and adds missing Turkish translations. johanjanssens, luminalpark, KalimeroMK, and quyt0 made their first contributions in this release.




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.