Repository navigation
Releases: php/frankenphp
Release list
v1.13.3
FrankenPHP 1.13.3 is a bug fix release. It fixes worker lookup for symlinked document roots, crashes caused by PHP extensions that install asynchronous signal handlers, and SIGILL crashes of musl static binaries on CPUs without SHA-NI.
🐛 Bug Fixes
- Worker mode: Workers are now matched when
resolve_root_symlink falseis set and the document root is a symlink. Since 1.13.0, requests ran on a regular thread and failed with "FrankenPHP must be in worker mode". Workers are now also indexed under their declared path by @dunglas in #2739. - Extensions:
SA_ONSTACKis now re-applied to the signal handlers that extensions install at startup, for every signal and not onlySIGSEGV,SIGBUSandSIGFPE. Asynchronous handlers without the flag, such as theSIGVTALRMhandler of dd-trace-php, overflowed the goroutine stack and crashed the Go runtime later during garbage collection by @mstegmeyer in #2746. - Static builds: musl binaries are now built for the baseline CPU of the architecture instead of the build host's CPU. Release binaries used SHA-NI and AVX-512 without runtime checks, so
hash('sha256')died withSIGILLon CPUs without SHA-NI. SetSPC_TARGET=native-native-muslto opt back into host-tuned builds by @dunglas in #2749.
📖 Documentation
- Update translations by @github-actions[bot] in #2733, #2742 and #2750, and fix their Markdown lint errors by @dunglas in #2752.
💖 New Contributors
- @mstegmeyer made their first contribution in #2746
Full Changelog: v1.13.2...v1.13.3
v1.13.2
FrankenPHP 1.13.2 is a security release. Its Docker images and static binaries are built with the PHP versions that fix PHP2Shell (CVE-2026-109618 and CVE-2026-109619), two critical vulnerabilities allowing unauthenticated remote code execution. It also fixes 4 vulnerabilities in FrankenPHP itself, including a high-severity one that let a local user get their PHP code executed by a service running an embedded app. It also makes frankenphp_log() available in php-cli, stops hot reload from serving stale pages, and moves static builds to static-php-cli v3. Static builds have a few changes worth reading in the upgrade notes below. Upgrading is strongly recommended.
🔒 Security
- Critical, PHP2Shell: Docker images are built with PHP 8.2.35, 8.3.36, 8.4.27 and 8.5.12, and static binaries with PHP 8.5.12. These versions fix CVE-2026-109618 and CVE-2026-109619, which allow unauthenticated remote code execution and have no complete mitigation besides upgrading (PHP security advisory).
- High: Embedded apps were extracted to a predictable directory in the shared temp dir, and existing files were reused. A local user could precreate that directory and get their PHP code executed by the service. Apps are now extracted atomically to a per-user directory, which is only reused when the current user owns it (GHSA-6q3j-62j9-f9hh).
- Requests waiting for a PHP thread are now dropped when their client disconnects. Before this fix, an unauthenticated client could queue requests, disconnect and keep the thread pool busy for everyone else (GHSA-fq24-r53r-xcx3).
- Cyclic arrays are now rejected when PHP values are converted to Go. A self-referencing array passed to
frankenphp_log(), returned by a worker callback or given to the extension API overflowed the stack and crashed the whole process (GHSA-ccgp-ch9h-533f). - Worker mode: The errors returned by
error_get_last()andlibxml_get_errors()are now cleared at the end of each request. Before this fix, they could leak fragments of a previous client's input and server paths into the next request served by the same thread (GHSA-4gfw-9v29-gp9h).
⚠️ Upgrade Notes
Static builds now use static-php-cli v3 by @henderkes in #2618:
- macOS: Binaries are only published for Apple Silicon. There is no
frankenphp-mac-x86_64asset anymore. On Intel Macs, build from source withbuild-static.sh. - glibc builds: The
-gnubinary now requires glibc 2.28 or later, instead of 2.17. - Static builder images: Run them with
docker runand passPHP_EXTENSIONS,PHP_EXTENSION_LIBS,XCADDY_ARGSand the other options as environment variables instead of Docker build args. The binary is written to the mounted/outputdirectory, and the defaults now live incraft.yml. See the static build docs.
✨ New Features
- CLI:
frankenphp_log()and theFRANKENPHP_LOG_LEVEL_*constants are now available inphp-cli, so code shared between web requests and commands can log the same way. Entries use the server's format and are written to stderr. TheCaddyfileisn't loaded, so itslogdirectives don't apply by @dunglas in #2719. - Extensions:
frankenphp_thread_index()is exported again for C extensions such as frankenstate and frankenasync by @nickchomey in #2717.
🐛 Bug Fixes
- Hot reload:
opcache.revalidate_freqnow defaults to0whenhot_reloadis configured. With the default of2, the page fetched right after a change often came back unchanged. A value set inphp.ini, inPHP_INI_SCAN_DIRor with thephp_inidirective still takes precedence by @dunglas in #2680. - Caddy: Thread and request counts are now parsed as
int. On 32-bit platforms (linux/386,linux/arm/v6andlinux/arm/v7), out-of-range values wrapped to negative numbers, somax_threads 4294967295meantauto. They now return a range error by @dunglas in #2722. - Static builds:
types.his renamed tofrankenphp_types.h, because libwebp's header with the same name shadowed it and broke the build by @alexandre-daubois in #2728.
📖 Documentation
- Explain how to publish Mercure updates from
php-clithrough the hub's POST endpoint, and why detectingmercure_publish()while warming caches disables it in the web server too by @dunglas in #2718. - Fix the Russian translation by @chubakur in #2712.
💖 New Contributors
- @chubakur made their first contribution in #2712
- @zaccesss made their first contribution in #2726
- @nickchomey made their first contribution in #2717
- @sergehardy made their first contribution in #1926
Full Changelog: v1.13.1...v1.13.2
v1.13.1
FrankenPHP 1.13.1 fixes the regressions reported since 1.13.0. Nextcloud and other apps that read $_SERVER['REQUEST_URI'] while booting can run in worker mode again, php-cli exposes $argv like the native CLI again (fixing Symfony Console), and Caddy workers retry failed boots as documented instead of giving up on the first error. It also fixes two process crashes and 500 errors during graceful shutdown. Static builds now include the deepclone extension, which speeds up Symfony's cache. Upgrading is recommended for all 1.13.0 users.
✨ New Features
- Static builds: Add deepclone to the default extensions. It is the native implementation of Symfony's
DeepCloner, used byVarExporter::export()(and so thecache.systemfiles) andArrayAdaptersince symfony/var-exporter 8.1, and is several times faster than the PHP polyfill on large object graphs by @soyuka in #2699.
🐛 Bug Fixes
- Workers: Set
$_SERVER['REQUEST_URI']to the script name while booting workers, as in 1.12.7. Nextcloud redirected and exited during boot, so its worker failed with "too many consecutive failures" by @dunglas in #2701. Fixes #2697. - Workers:
max_consecutive_failuresnow defaults to 6 for workers declared in a Caddyfile or JSON config, as documented. Previously, any boot failure aborted startup without retrying. An explicit0still disables retries by @dunglas in #2704. - CLI:
php-clisets$argv[0]to the script path (orStandard input codewith-r) like the native CLI SAPI, instead of the binary name followed by a shifted script path. This broke Symfony Console and other tools reading$argvby @henderkes in #2691. Fixes #2690. - Graceful shutdown: Keep PHP running until Caddy has stopped all apps, so requests served during
shutdown_delayand in-flight requests no longer get HTTP 500 errors by @nesl247 in #2645. Fixes #2644. - Threads: Fix a segfault of the whole process when restarting an unhealthy thread, a regression since 1.12.4 by @BastienClement in #2696. Fixes #2688.
- Request body: Don't read the request body after the request has finished. With
fastcgi_finish_request()orfrankenphp_finish_request()over HTTP/2, PHP's shutdown drained the body after the handler returned, which could crash the process by @dunglas in #2702. Fixes #2694. - Forked processes: Only tie direct children of FrankenPHP to its lifetime, so processes that double-fork no longer kill their grandchildren. This broke the gnupg extension ("could not init keylist"), a regression since 1.12.5 by @dunglas in #2703. Fixes #2689.
- Mercure: Exclude the hub from
encodein the sampleCaddyfileand document the pattern. Since Caddy 2.11.7 compresses server-sent events, each subscriber held a compression encoder (a few MiB with zstd), and compressing private updates next to attacker-influenced data enables BREACH-style attacks.php-server --mercureisn't affected by @dunglas in #2692.
💖 New Contributors
- @nesl247 made their first contribution in #2645
- @BastienClement made their first contribution in #2696
Full Changelog: v1.13.0...v1.13.1
v1.13.0
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 turni...
v1.12.7
FrankenPHP 1.12.7 fixes a bug where output written after fastcgi_finish_request()/frankenphp_finish_request() was silently discarded in classic (non-worker) mode: with the default ignore_user_abort, echoing anything after finishing the request incorrectly marked the connection as aborted and bailed out mid-script. It also quiets noisy debug-level logging for routine client disconnects and fixes an incorrect thread-count metric.
🐛 Bug Fixes
- Classic mode: Fix output written after
fastcgi_finish_request()/frankenphp_finish_request()being silently discarded. With the defaultignore_user_abort, echoing anything after finishing the request incorrectly marked the connection as aborted and bailed out mid-script by @dunglas in #2569. Fixes #2548. - Metrics: Report
frankenphp_total_threadsas a gauge instead of accumulating it, so the metric reflects the current thread count instead of growing unbounded by @ousamabenyounes in #2550. Fixes #2481. - Opcache: Remove the opcache-invalidation restart hook again; restarting on every opcache reset could cascade into recursive restarts for frameworks that invalidate the cache heavily by @AlliBalliBaba in #2564.
- Logging: Stop logging routine client-disconnect write/flush errors at
warnlevel; they're nowdebug, matching Caddy and Mercure's convention, and no longer mislabeled as "not a flusher" by @dunglas in #2589. - Build: Take the
PHP_CONFIGenvironment variable into account ingo.shby @henderkes in #2557.
📖 Documentation
💖 New Contributors
- @ousamabenyounes made their first contribution in #2550
Full Changelog: v1.12.6...v1.12.7
v1.12.6
FrankenPHP 1.12.6 fixes a crash that could take down the whole process on HTTP/2. If a PHP script calls frankenphp_finish_request() (or closes its context early) and then lazily reads php://input — common with enable_post_data_reading=Off — FrankenPHP set a read deadline on an already-finalized HTTP/2 stream and segfaulted. HTTP/1 was unaffected. Anyone serving over HTTP/2 should upgrade.
🐛 Bug Fixes
- HTTP/2: Don't set a request body read deadline after the request is finished. Reading
php://inputafterfrankenphp_finish_request()dereferenced the nil'd-out HTTP/2 stream state and crashed the process; the deadline is now skipped once the context is done by @dunglas in #2538. Fixes #2535.
📖 Documentation
- Add a PSR-15 worker example by @dunglas in #2539.
- Remove title case in docs by @alexandre-daubois in #2534.
- Update translations by @github-actions in #2537.
Full Changelog: v1.12.5...v1.12.6
v1.12.5
FrankenPHP 1.12.5 is a security release. It fixes two disclosed vulnerabilities — an information leak in the default Docker welcome page and a cross-request session ID leak in worker mode — plus a batch of crash and hang fixes in worker mode and the CGI layer. Every user should upgrade, especially anyone running the official Docker images or the session extension as a shared module.
🔒 Security & Hardening
- Docker images no longer serve
phpinfo()by default. The default/app/public/index.phpranphpinfo(), so any derived image that didn't ship its own index leaked environment variables (often holding secrets),php.inidirectives, versions, and system paths. Both the Debian and Alpine images now ship the same static, script-free welcome page used by the RPM/deb packages by @dunglas in #2524. See GHSA-cj57-c655-p798. - Session ID no longer leaks across worker requests when the session extension is a shared module.
PS(id)wasn't reset between requests whenHAVE_PHP_SESSIONwas undefined at compile time, sosession_start()could reuse the previous request's session id and hand one client another client's session. Statically-linked builds were unaffected by @dunglas in #2523. See GHSA-v3ph-cgqh-r8p5. - Slow-POST protection by default. A client that stalls a request body indefinitely used to hold a PHP thread for the life of the connection, exhausting the thread pool under
max_threads.php_server/phpdeployments now enforce a 60s idle timeout on body reads out of the box (matching nginx'sclient_body_timeout), configurable via the newrequest_body_timeoutdirective or0to disable. Library users get the same control viaWithRequestBodyTimeoutby @iliaal in #2465.
🐛 Fixes
- Fix a heap-buffer-overflow read when parsing a short
header("HTTP/...")status line by assuming the code from PHP's ownsapi_update_response_code()instead of reparsing it by @iliaal in #2464. - Guard against a panic (and process crash) from a malformed
RemoteAddrwhen stripping IPv6 brackets in server variable registration by @iliaal in #2463, and a related panic on malformedRemoteAddrin the CGI variable builder by @alexandre-daubois in #2484. - Fix queued requests getting stuck on replaced channels by reusing created-once channels by @henderkes in #2470.
- Fix long-running CLI scripts hanging when a
pcntlsignal reached the Go runtime instead of the PHP handler by @henderkes in #2445. - Fix hanging reboots on threads that were upscaled and then downscaled by @AlliBalliBaba in #2510, following up on the thread-reboot-on-
opcache_reset/watcher-change fix by @AlliBalliBaba in #2364. - Deduplicate registration-time duplicates produced by
matchdirectives by @henderkes in #2487. php_server's environment variables are now available in the sandboxed environment by @henderkes in #2451.- extgen: correct a C parameter cast in generated class method wrapper calls by @alexandre-daubois in #2436.
⚡ Performance Improvements
- Fold a 3-instruction TLS load into a single
mrson aarch64 by @henderkes in #2512.
📖 Documentation
- Add guides for deploying on macOS and Windows by @m-this.
- Document
pcovandimagickincompatibilities in known issues by @alexandre-daubois in #2492. - Chinese translations for logging and observability docs by @pierresh in #2515.
- Italian translation of the docs by @garak in #2405.
💖 New Contributors
- @iliaal made their first contribution in #2464.
- @m-this made their first contribution in #2502.
- @garak made their first contribution in #2405.
Need help hardening a worker-mode deployment against session leaks and slow-POST DoS, or auditing your Docker images before they ship secrets to the public? Les-Tilleuls.coop — the team behind FrankenPHP — provides professional support, consulting, custom development, and training. Get in touch: contact@les-tilleuls.coop.
Full Changelog: v1.12.4...v1.12.5
v1.12.4
FrankenPHP 1.12.4 is a hardening and stability release. It pulls in upstream security fixes from Caddy 2.11.4 and Mercure 0.24.2, closes a class of HTTP header spoofing, and fixes several crashes and data races in worker mode. Every user should upgrade.
The headline is defense-in-depth against underscore-header spoofing. CGI maps dashes to underscores (Foo-Bar becomes HTTP_FOO_BAR), so a client-supplied Foo_Bar header is indistinguishable from a legitimate Foo-Bar in $_SERVER and can spoof any header an app or upstream proxy trusts (forwarded-for, auth, etc.). The bundled Caddy 2.11.4 now ignores header fields containing underscores at the server layer, and FrankenPHP documents the risk for code using the Go API directly.
🔒 Security & Hardening
- Underscore header spoofing blocked at the server layer. The bundled Caddy 2.11.4 now ignores HTTP header fields whose name contains an underscore, preventing collisions with the dash-to-underscore CGI mapping (reported by @Vincent550102, patched by @dunglas upstream).
NewRequestWithContextnow documents the risk for direct Go API users by @dunglas in #2460. - Caddy 2.11.4 security patches bundled: TLS client-auth fix, Windows backslash normalization in the path matcher, rewrite placeholder re-expansion fix, and a patch for GHSA-vcc4-2c75-vc9v. See the Caddy 2.11.4 release notes.
- Mercure 0.24.2 security hardening bundled for the Mercure Caddy module: SSE field injection via
id/type(CWE-93) now rejected, reserved/.well-known/mercuretopic forgery blocked,Last-Event-IDmetadata disclosure fixed, and DoS amplification caps added. See the Mercure 0.24.2 release notes. - Security model documentation describing FrankenPHP's trust boundaries and what qualifies as a security issue by @alexandre-daubois in #2455.
🐛 Fixes
- Fix
ext-parallelcrashes by correctly propagating the parent thread index viaSG(server_context)by @henderkes in #2438. - Clear
in_save_handlerstate that blocked the subsequent close handler by @henderkes in #2443. - Fix a data race in metrics by replacing the mutex with a read-write mutex by @alexandre-daubois in #2450 and removing redundant shutdown assignments by @henderkes in #2452.
- Report
headers_sent()asfalseunder CLI emulation by @henderkes in #2453.
⚡ Internal Improvements
- Drop the unreachable space-to-underscore replacement in header names (Go's net/http already rejects spaces) by @dunglas in #2441.
- Make UPX packing opt-in via the
COMPRESSenv var by @dunglas in #2429. - Dependency updates including Caddy 2.11.4 and Mercure 0.24.2 in #2454 and #2462.
📖 Documentation
- Add a WoltLab Suite example by @SoftCreatR in #2428.
- Update the incompatibilities section by @henderkes in #2420.
💖 New Contributors
- @SoftCreatR made their first contribution in #2428.
Need help adopting FrankenPHP, hardening a PHP application against header-spoofing and real-time security issues like these, or auditing your worker setup for races? Les-Tilleuls.coop — the team behind FrankenPHP — provides professional support, consulting, custom development, and training. Get in touch: contact@les-tilleuls.coop.
Full Changelog: v1.12.3...v1.12.4
v1.12.3
This release fixes CVE-2026-45062 (high, CVSS 8.1): unsafe Unicode handling in CGI path splitting let an attacker have a non-.php file executed as PHP via a crafted URL, in any deployment where attacker-controlled file names land on the served filesystem. All users on v1.11.2 through v1.12.2 should upgrade.
It also brings a ~7-8% Hello World throughput bump from a refreshed PGO profile, configurable per-thread request limits, persistent-zval helpers for sharing state across threads, a cross-platform force-kill primitive for stuck PHP threads, correct SCRIPT_NAME / PHP_SELF / PATH_INFO server variables, and a long series of frankenphp extension-init (extgen) generator fixes by @alexandre-daubois.
Released binaries now carry SLSA build-provenance attestations — verify with gh attestation verify <binary> --owner php or gh attestation verify oci://docker.io/dunglas/frankenphp@sha256:... --owner php.
🔒 Security
- Unsafe Unicode handling in CGI path splitting allowed execution of non-PHP files (GHSA-3g8v-8r37-cgjm / CVE-2026-45062). Reported by @KC1zs4.
🚀 Features
- Configurable
max_requestsfor PHP threads by @nicolas-grekas in #2292 - Persistent-zval helpers (deep-copy zval trees across threads) by @nicolas-grekas in #2366
- Cross-platform force-kill primitive for stuck PHP threads by @nicolas-grekas in #2365
- Release binaries now ship with SLSA build-provenance attestations by @dunglas in #2418
🐛 Fixes
- Set
$_SERVERvariablesSCRIPT_NAME,PHP_SELF, andPATH_INFOcorrectly by @henderkes in #2317 - Fix dead forked
pthread_forkchildren by @henderkes in #2332 - Fix upstream BC break on
INI_INT()macro by @zeriyoshi in #2387 - Caddy: reject invalid
split_pathat provision time by @alexandre-daubois in #2350 extgenparser hardening by @alexandre-daubois: better error handling (#2370), emit warnings to stderr (#2374), resetiotaper const block (#2375), escape control chars in C string literals (#2377), extract Go function bodies viago/ast(#2379), symmetric Go type compatibility check (#2380)
⚡ Performance and Internal Improvements
- Use PGO to improve FrankenPHP's Go performance (7-8% Hello World throughput) by @henderkes in #2361
perf(extgen): hoist const block regexes out of parser loop by @alexandre-daubois in #2378refactor: adddrain()seam tothreadHandlerinterface by @nicolas-grekas in #2367refactor(extgen): share signature and parameter parsing helpers by @alexandre-daubois in #2376
📝 Documentation
- Improve worker docs, add internals docs by @dunglas in #2334
- Add SEO frontmatter,
llms.txt, and code-block hygiene by @dunglas in #2394 - Fix migration guide menu entry by @alexandre-daubois in #2373
- Adjust volume mount path in
migrate.mdby @francislavoie in #2337 - Fix Laravel trusted proxies URL by @mtmn in #2359
- Update wording in extensions documentation by @SpencerMalone in #2338
💖 New Contributors
- @zeriyoshi made their first contribution in #2387
- @mtmn made their first contribution in #2359
Need help adopting FrankenPHP, hardening a PHP application against issues like CVE-2026-45062, or squeezing more performance out of your workers? Les-Tilleuls.coop — the team behind FrankenPHP — provides professional support, consulting, custom development, and training. Get in touch: contact@les-tilleuls.coop.
Full Changelog: v1.12.2...v1.12.3
v1.12.2
This release includes several new features, bug fixes, and performance improvements. It also addresses a critical cache key collision vulnerability in Mercure (GHSA-hwr4-mq23-wcv5).
🚀 Features
- Add more metrics in
ThreadDebugStateby @alexandre-daubois in #2282
🐛 Fixes
- Fix fatal PHP shutdowns by @AlliBalliBaba in #2293
- Fix race condition in auto-scaler during Caddy config reload by @nicolas-grekas in #2318
- Fix
opcache_preloadin PHP 8.2 by @AlliBalliBaba in #2284 - Force reload in FrankenPHP service configuration by @kitro in #2281
- Skip
README.mdgeneration inextension-initcommand if it exists by @IndraGunawan in #2283 - Remove
cdbefore call tospc dump-extensionsby @fastnloud in #2328
⚡ Performance and Internal Improvements
- Extend table on environment startup instead of letting
zend_hash_copydo it by @henderkes in #2272 - Hoist
LoaderFuncto package-level variable inphpheadersby @dunglas in #2053
📝 Documentation
- Add Symfony-specific page by @dunglas
- Add migration guide by @alexandre-daubois
- Add observability page by @alexandre-daubois
- Add trusted proxies section by @alexandre-daubois
- Add autocompletion documentation by @alexandre-daubois
- Improve Dockerfile for hardened images by @dunglas
- Update extension generator documentation for a more natural order by @alexandre-daubois
- Add IDE (GoLand/CLion) setup to
CONTRIBUTING.mdby @henderkes - Add French translation for hot reloading by @Ehyiah
💖 New Contributors
- @Ehyiah made their first contribution in #2105
- @nicolas-grekas made their first contribution in #2294
- @kitro made their first contribution in #2281
Full Changelog: v1.12.1...v1.12.2