FrankenPHP 1.13.0 is a big one. It upgrades to Caddy 2.11.7, bringing Slowloris protection, the new url_pattern matcher, the Incremental header for streaming apps and many reverse proxy fixes. It ships with Mercure 1.0, the stable version of the real-time protocol. Configuration reloads are now safe: an invalid configuration is rejected before it replaces the running one, instead of leaving the server answering 500. This release also fixes 5 security vulnerabilities, 2 of them rated high. Upgrading is strongly recommended, and the upgrade notes below are worth a read first. Need help with the upgrade or with sizing threads and workers? Les-Tilleuls.coop, the company behind FrankenPHP, provides professional support, performance audits, custom development and training: contact@les-tilleuls.coop.
🔒 Security
- High: Fix a document-root escape in
SCRIPT_FILENAMEresolution on Windows (GHSA-868c-7h7m-mmj9). - High: Fix header spoofing through dot-form header names: PHP maps
Foo.BartoHTTP_FOO_BARlikeFoo-Bar. Caddy 2.11.7 now drops such headers by default, and theNewRequestWithContext()documentation warns library users (GHSA-qcrp-8483-f2f2). - Require a path segment boundary when splitting the CGI path, so
/uploads/a.php.txt/b.phpno longer executesuploads/a.php(GHSA-xxjp-cjxr-2x6m). putenv()no longer writes to the process-wide OS environment, which leaked values across requests and threads (GHSA-996f-w38m-f574).- Fix a crash of the whole server process when
frankenphp_log()receives a crafted array (GHSA-4prg-hv4r-g6mv).
🌐 Caddy 2.11.7
FrankenPHP 1.12.7 shipped Caddy 2.11.4. This release includes everything from Caddy 2.11.6 and 2.11.7, including:
- Slowloris protection: idle read/write timeouts reset on every successful read or write, so stalled connections are cut off while slow but progressing ones are left alone. Tune them per route with the new
timeoutsdirective by @dunglas in caddy#7913. url_patternmatcher: match requests with the URLPattern web standard (named groups, wildcards, regexps), the same syntax used by browsers and many frameworks. Captured groups become placeholders by @dunglas in caddy#7787.Incrementalheader (RFC 10036): the standard replacement for NGINX'sX-Accel-Buffering. Responses withIncremental: ?1are streamed immediately byreverse_proxyandencodeby @dunglas in caddy#8020.- Server-sent events behind
encodenow stream immediately instead of being buffered, which benefits Mercure by @SillyZir in caddy#7905. - Graceful shutdown waits for servers left over from previous configurations, so long-lived responses that started before a reload aren't cut off by @dunglas in caddy#8009.
tls_automate_namesglobal option to manage certificates for names not served by a site block by @IslamElsayed in caddy#8015.- Faster TLS handshakes: certificate lookup is about twice as fast, with 10 allocations instead of 15 by @u5surf in caddy#8010.
- Reverse proxy: partial responses are flushed properly by @WeidiDeng in caddy#7849, TCP half-close is propagated on upgraded streams by @btncwn in caddy#8027, and active health checks are isolated per check config by @SillyZir in caddy#7916.
expected_underscore_headers(by @bluegate-studio in caddy#7809) andexpected_dot_headerskeep specific headers that Caddy now drops.
📡 Mercure 1.0
FrankenPHP embeds Mercure 1.0 by @dunglas in #2611:
mercure_publish()now throws aValueErrorfor invalid updates (topic in the reserved/.well-known/mercurenamespace, ID starting with#, control characters, invalid UTF-8, negativeretry, no topic) and aRuntimeExceptioncarrying the hub's message when the dispatch fails.- Publisher and subscriber keys are bound to a trusted issuer with the new
issuerblock. The sampleCaddyfileandphp-server --mercureuse it.
⚠️ Upgrade Notes
num_threadsnow counts the threads for requests no worker serves; worker threads come on top. A configuration that setsnum_threadswith workers declared starts more threads than before. Ifmax_threadsis belownum_threadsplus the worker threads, startup now fails with an error naming the three numbers. Defaults are unchanged (#2660).- Mercure: setting
publisher_jwtorsubscriber_jwtwithoutprotocol_version_compatibilityis now a configuration error. Bind keys to a trusted issuer with the newissuerblock instead (see #2611 and the sampleCaddyfile). Hot reloading follows the protocol:$_SERVER['FRANKENPHP_HOT_RELOAD']now advertises/.well-known/mercure?match=<topic>, so update any URL hardcoded with?topic=. - Caddy: request headers are now limited to 16 KiB by default (raise it with
max_header_size), stalled reads and writes are aborted after 1 minute, headers containing.are dropped like those containing_already were, a wildcard site'sclient_authno longer applies to more specific sites, and some invalid configurations are now rejected. See the Caddy 2.11.6 breaking changes. - Building PHP without
--disable-zend-signals(ZTS) now makes FrankenPHP refuse to start with an explicit error, instead of hanging while memory grows (#2639).
✨ New Features
- Extensions: Expose a thread API for Go-based PHP extensions:
Thread(index)to get the request bound to a PHP thread,PHPThread.Pin()to keep Go objects alive,PHPThread.IsRequestDone(), and thefrankenphp_thread_index()C function by @johanjanssens in #2305. - Config reloads: Validate a configuration before it replaces the running one. A missing worker file, a duplicated worker name or an inconsistent thread budget is now rejected and the running configuration keeps serving. Library users get
frankenphp.Validate()by @nicolas-grekas in #2661. - Threads:
num_threadscounts the threads left for regular requests, and the startup log reportstotal_threadsandworker_threadsby @nicolas-grekas in #2660. - Opcache: Log opcache shared-memory restarts and count them in the new experimental
frankenphp_opcache_restarts{reason}metric (PHP 8.4+). These restarts are unsafe under ZTS, so the counter should stay at zero by @nicolas-grekas in #2634. php_server: Mirrorphp_serverblocks on the FrankenPHP side, so requests and workers are properly scoped to their block by @AlliBalliBaba in #2499.- phpinfo:
phpinfo()now has a FrankenPHP section listing the Caddy version, and Go code can add rows withfrankenphp.AddPHPInfoEntry()by @henderkes in #2578. - CLI:
php-cliuses PHP's built-in CLI SAPI on PHP 8.5+ by @withinboredom in #1757. - PHP 8.6: Compatibility with PHP 8.6 by @henderkes in #2600.
- Upgrade to Go 1.27 by @alexandre-daubois in #2626.
🐛 Bug Fixes
- Startup: Don't hang requests that arrive before regular threads are ready, which showed up as random 499/504 errors on the first requests after a container start by @ptondereau in #2612.
- Workers: Log worker crashes at
warnlevel with their exit status, instead ofdebuglike a clean restart by @luminalpark in #2602. - Shutdown: Fix
Shutdown()hanging forever on a worker that gave up during boot pastmax_consecutive_failuresby @nicolas-grekas in #2662 and #2664. - Static builds: Stop the bundled
parallelextension from turning recoverable Go faults into process crashes by @henderkes in #2651. Fixes #2650. - Extensions: Fix a possible use-after-free of the extensions array retained by
register_extensionsby @henderkes in #2646. Fixes #2629. - extgen: Fix grouped and nullable parameters, callables,
mixedreturn values, method wrappers, constants and integer literals in generated extensions by @alexandre-daubois in #2596.
📖 Documentation
- List runtime engine settings that persist between requests in worker mode (
ini_set(),stream_context_set_default(),date_default_timezone_set(),chdir()…) by @dunglas in #2682. - Document HTTP request filtering differences by @ousamabenyounes in #2561.
- Enhance the metrics documentation by @alexandre-daubois in #2316.
- Add a Yii 3 page by @KalimeroMK in #2615.
- Fix the broken Idiomorph CDN URL in the hot reload snippet by @quyt0 in #2642.
- Add missing Turkish translations by @mertingen in #2663.
💖 New Contributors
- @johanjanssens made their first contribution in #2305
- @luminalpark made their first contribution in #2602
- @KalimeroMK made their first contribution in #2615
- @quyt0 made their first contribution in #2642
Full Changelog: v1.12.7...v1.13.0