Home PHP FrankenPHP & Modern PHP Runtimes: A Deep Dive into High-Performance PHP
Advanced 3 min · July 13, 2026

FrankenPHP & Modern PHP Runtimes: A Deep Dive into High-Performance PHP

Explore FrankenPHP, a modern PHP runtime built on Caddy and Go.

N
Naren Founder & Principal Engineer

20+ years shipping production PHP systems at scale. Drawn from code that ran under real load.

Follow
Production
production tested
July 13, 2026
last updated
2,073
articles · all by Naren
Before you start⏱ 20-25 min read
  • PHP 8.1 or higher
  • Basic knowledge of PHP and web servers
  • Familiarity with Caddy or Nginx configuration
 ● Production Incident 🔎 Debug Guide ⚙ Triage Commands
Quick Answer
  • FrankenPHP is a modern PHP application server built on top of Caddy and Go.
  • It supports worker mode for persistent scripts, eliminating cold starts.
  • Built-in support for early hints, HTTP/2, HTTP/3, and automatic HTTPS.
  • Can handle concurrent requests with a single PHP-FPM process.
  • Integrates seamlessly with Symfony, Laravel, and other frameworks.
✦ Definition~90s read
What is FrankenPHP and Modern PHP Runtimes?

FrankenPHP is a modern PHP application server built on Caddy and Go that provides worker mode, early hints, and concurrent request handling for high-performance PHP applications.

Think of PHP as a chef who prepares a meal from scratch every time a customer orders.
Plain-English First

Think of PHP as a chef who prepares a meal from scratch every time a customer orders. FrankenPHP is like a sous chef who stays in the kitchen, preps ingredients ahead, and can handle multiple orders at once without restarting. This makes the restaurant (your web app) much faster and more efficient.

PHP has long been the workhorse of the web, powering millions of websites. However, traditional PHP runtimes like FPM have inherent limitations: each request starts a new process, loads the entire application, and then tears it down. This leads to cold starts and wasted resources. Enter FrankenPHP, a modern PHP application server that reimagines how PHP runs. Built on top of Caddy (a Go-based web server) and the Go library for PHP, FrankenPHP introduces a worker mode that keeps your application in memory across requests. It also supports early hints, HTTP/2 and HTTP/3, automatic HTTPS, and concurrent request handling within a single PHP process. This tutorial will guide you through setting up FrankenPHP, understanding its architecture, and leveraging its features to build high-performance PHP applications. Whether you're running a Laravel API, a Symfony site, or a custom PHP app, FrankenPHP can dramatically reduce latency and improve throughput. We'll cover installation, configuration, worker mode, debugging, and real-world production considerations. By the end, you'll have a solid grasp of how to deploy PHP applications with modern runtimes for maximum performance.

What is FrankenPHP?

FrankenPHP is a modern PHP application server written in Go that integrates with Caddy. It replaces traditional PHP-FPM with a more efficient model. Key features include: worker mode (persistent scripts), early hints (103 status code), support for HTTP/2 and HTTP/3, automatic HTTPS via Let's Encrypt, and the ability to handle concurrent requests within a single PHP process. It's designed to be a drop-in replacement for PHP-FPM, compatible with existing PHP applications. FrankenPHP can also be used as a standalone server or as a Caddy module. Its architecture leverages Go's concurrency model to handle many requests with low overhead.

frankenphp-intro.phpPHP
1
2
3
4
5
6
<?php
// A simple PHP script to demonstrate FrankenPHP's worker mode
// This script will be kept in memory across requests

echo "Hello from FrankenPHP! Request #" . ($_SERVER['REQUEST_ID'] ?? 'unknown') . "\n";
echo "Memory usage: " . memory_get_usage(true) . " bytes\n";
Output
Hello from FrankenPHP! Request #1
Memory usage: 2097152 bytes
🔥Worker Mode
📊 Production Insight
Worker mode can cause memory leaks if your code isn't careful. Always test for memory growth over many requests.
🎯 Key Takeaway
FrankenPHP is a high-performance PHP server that keeps your app in memory across requests, reducing latency.

Installation and Setup

Installing FrankenPHP is straightforward. You can download a prebuilt binary from the official GitHub releases or compile from source. For this tutorial, we'll use the binary. First, download the appropriate version for your OS. Then, make it executable and optionally move it to your PATH. Create a simple Caddyfile to configure FrankenPHP. Here's a minimal example:

``` { frankenphp }

localhost:8080 { root * /path/to/your/app php_fastcgi unix//var/run/frankenphp.sock } ```

Then run frankenphp run to start the server. You can also use frankenphp --worker to enable worker mode. For production, you'll want to set up systemd service or use Docker. The official Docker image is available as dunglas/frankenphp.

CaddyfilePHP
1
2
3
4
5
6
7
8
9
10
11
{
    # Global configuration
    frankenphp
}

localhost:8080 {
    root * /var/www/public
    php_fastcgi unix//var/run/frankenphp.sock
    encode gzip
    file_server
}
💡Docker Setup
📊 Production Insight
For production, use a systemd service to manage FrankenPHP. Set Restart=always to handle crashes.
🎯 Key Takeaway
Installation is simple: download binary, create a Caddyfile, and run. Docker images are also available.

Worker Mode Deep Dive

Worker mode is the flagship feature of FrankenPHP. Instead of starting a new PHP process for each request, a single script is loaded once and handles multiple requests sequentially. This dramatically reduces overhead, especially for frameworks with heavy bootstrapping (like Laravel or Symfony). To enable worker mode, add the worker directive to your Caddyfile or pass --worker flag. The script must be a PHP file that returns a callable or uses FrankenPHP\handleRequest function. Here's an example of a worker script:

```php <?php // worker.php require_once __DIR__ . '/vendor/autoload.php';

$app = require_once __DIR__ . '/bootstrap/app.php'; $kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);

return function ($request) use ($kernel) { $response = $kernel->handle($request); $kernel->terminate($request, $response); return $response; }; ```

Then in Caddyfile: php_fastcgi unix//var/run/frankenphp.sock with worker directive. FrankenPHP will keep the script running and pass requests to it.

worker.phpPHP
1
2
3
4
5
6
7
8
9
10
11
12
<?php
// Example worker script for Laravel
require_once __DIR__ . '/vendor/autoload.php';

$app = require_once __DIR__ . '/bootstrap/app.php';
$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);

return function ($request) use ($kernel) {
    $response = $kernel->handle($request);
    $kernel->terminate($request, $response);
    return $response;
};
⚠ Memory Leaks
📊 Production Insight
Use gc_collect_cycles() periodically in long-running workers to free cyclic references.
🎯 Key Takeaway
Worker mode keeps your app in memory, eliminating cold starts. Ensure your code is memory-safe.

Early Hints (103 Status Code)

Early hints allow the server to send preliminary HTTP headers before the final response, enabling the browser to start loading critical resources (like CSS, JS, images) earlier. FrankenPHP supports early hints via the 103 Early Hints status code. To use it, your application must send Link headers with rel=preload or rel=preconnect. FrankenPHP will automatically send these as early hints. For example, in a PHP script:

``php <?php header('Link: </style.css>; rel=preload; as=style'); // ... later generate the full response ``

FrankenPHP will send a 103 response with the Link header before the final 200 response. This can improve perceived performance significantly. You can also configure Caddy to add early hints for static assets.

early-hints.phpPHP
1
2
3
4
5
6
7
<?php
// Send early hints for critical CSS
header('Link: </style.css>; rel=preload; as=style');
header('Link: </script.js>; rel=preload; as=script');
// Simulate some work
sleep(1);
echo "<html><head><link rel='stylesheet' href='/style.css'></head><body>Hello</body></html>";
Output
HTTP/1.1 103 Early Hints
Link: </style.css>; rel=preload; as=style
Link: </script.js>; rel=preload; as=script
HTTP/1.1 200 OK
Content-Type: text/html
<html>...
💡Browser Support
📊 Production Insight
Use early hints for above-the-fold resources. Overusing them can cause unnecessary preloads.
🎯 Key Takeaway
Early hints speed up page loads by telling the browser about critical resources early.

Concurrent Request Handling

Unlike traditional PHP-FPM which uses multiple processes, FrankenPHP can handle multiple concurrent requests within a single PHP process using its Go-based event loop. This is achieved through the FrankenPHP\handleRequest function and coroutines. However, PHP itself is single-threaded, so concurrency is cooperative. You can use FrankenPHP\async to run non-blocking I/O operations. For example:

```php <?php use function FrankenPHP\async;

$result = async(function () { // Simulate non-blocking I/O return file_get_contents('https://api.example.com'); });

echo $result->await(); ```

This allows handling multiple requests while waiting for I/O. Note that this is not parallelism; it's concurrency within a single process. For CPU-bound tasks, you'll still need multiple workers.

concurrent.phpPHP
1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
use function FrankenPHP\async;

$promises = [];
foreach (['https://api1.example.com', 'https://api2.example.com'] as $url) {
    $promises[] = async(function () use ($url) {
        return file_get_contents($url);
    });
}

foreach ($promises as $promise) {
    echo $promise->await();
}
Output
Response from API1
Response from API2
🔥Concurrency vs Parallelism
📊 Production Insight
Avoid blocking operations like sleep() in concurrent mode; use async equivalents.
🎯 Key Takeaway
FrankenPHP allows concurrent I/O within a single process, improving throughput for I/O-bound applications.

Security Best Practices with FrankenPHP

Running PHP in worker mode introduces security considerations. Since the application persists, any global state changes can affect subsequent requests. Ensure you reset global variables and close database connections after each request. Use register_shutdown_function to clean up. Also, be aware of file permissions: the worker runs as the user that started FrankenPHP. Use proper file ownership and avoid running as root. For HTTPS, FrankenPHP automatically provisions Let's Encrypt certificates via Caddy. Ensure your domain is correctly configured. Additionally, consider using OPcache to cache compiled PHP files. In worker mode, OPcache is especially beneficial because the cache persists across requests. Configure OPcache with opcache.enable=1 and opcache.revalidate_freq=0 for production.

security.phpPHP
1
2
3
4
5
6
7
8
9
<?php
// Reset global state after each request
register_shutdown_function(function () {
    // Close database connections
    // Clear global variables
    foreach (get_defined_vars() as $key => $value) {
        unset($GLOBALS[$key]);
    }
});
⚠ Global State
📊 Production Insight
Use a middleware or event listener to reset state after each request in frameworks.
🎯 Key Takeaway
Worker mode requires careful state management. Use shutdown functions to clean up.
● Production incidentPOST-MORTEMseverity: high

The 10-Second Cold Start: How FrankenPHP Saved a Laravel API

Symptom
Users experienced 10-second response times on the first request after a new deployment, but subsequent requests were fast.
Assumption
The developer assumed it was a database connection issue or slow query.
Root cause
PHP-FPM was starting a new process for each request, and Laravel's service provider bootstrapping was slow on cold start.
Fix
Switched to FrankenPHP with worker mode, which kept the Laravel application in memory across requests, eliminating cold starts.
Key lesson
  • Worker mode eliminates cold starts by keeping the app in memory.
  • Always profile first request latency separately.
  • FrankenPHP is especially beneficial for frameworks with heavy bootstrapping.
  • Combine with OPcache for maximum performance.
  • Monitor memory usage in worker mode to avoid leaks.
Production debug guideSymptom to Action4 entries
Symptom · 01
High memory usage in worker mode
Fix
Check for memory leaks in your PHP code. Use memory_get_usage() at the end of requests. Consider restarting workers periodically.
Symptom · 02
Requests hanging or timing out
Fix
Check FrankenPHP logs. Ensure max_execution_time is set appropriately. Look for blocking I/O operations.
Symptom · 03
Early hints not working
Fix
Verify that your application sends Link headers. Check Caddy configuration for early hints directive.
Symptom · 04
Worker crashes without error
Fix
Enable verbose logging in FrankenPHP. Use 'frankenphp --verbose' to see worker output.
★ Quick Debug Cheat Sheet for FrankenPHPCommon issues and immediate actions
Worker not starting
Immediate action
Check PHP version compatibility
Commands
php -v
frankenphp version
Fix now
Ensure PHP 8.1+ and correct binary path
Slow first request+
Immediate action
Enable worker mode
Commands
frankenphp --worker
Check logs
Fix now
Add 'worker' directive to Caddyfile
Memory leak+
Immediate action
Restart worker
Commands
kill -USR2 <worker-pid>
monitor memory
Fix now
Implement graceful restart in deploy script
FeaturePHP-FPMFrankenPHP
Process ModelMultiple processes (one per request)Single process with worker mode
Cold StartYes, each request bootstraps appNo, app persists in memory
Early HintsNot supportedBuilt-in support
HTTP/2 & HTTP/3Requires external serverNative support via Caddy
Automatic HTTPSNot built-inAutomatic via Let's Encrypt
ConcurrencyMultiple processesCooperative concurrency within process
⚙ Quick Reference
6 commands from this guide
FileCommand / CodePurpose
frankenphp-intro.phpecho "Hello from FrankenPHP! Request #" . ($_SERVER['REQUEST_ID'] ?? 'unknown') ...What is FrankenPHP?
Caddyfile{Installation and Setup
worker.phprequire_once __DIR__ . '/vendor/autoload.php';Worker Mode Deep Dive
early-hints.phpheader('Link: ; rel=preload; as=style');Early Hints (103 Status Code)
concurrent.phpuse function FrankenPHP\async;Concurrent Request Handling
security.phpregister_shutdown_function(function () {Security Best Practices with FrankenPHP

Key takeaways

1
FrankenPHP is a modern PHP runtime that boosts performance via worker mode, early hints, and concurrency.
2
Worker mode eliminates cold starts but requires careful state management to avoid memory leaks.
3
Early hints improve perceived page load times by preloading critical resources.
4
Concurrent request handling allows non-blocking I/O within a single PHP process.
5
FrankenPHP integrates seamlessly with Caddy for automatic HTTPS and HTTP/2/3 support.

Common mistakes to avoid

3 patterns
×

Not resetting global state in worker mode

×

Using blocking I/O in concurrent mode

×

Ignoring memory growth in worker mode

INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01SENIOR
Explain how worker mode in FrankenPHP improves performance.
Q02SENIOR
What are early hints and how does FrankenPHP implement them?
Q03SENIOR
How does FrankenPHP handle concurrent requests within a single PHP proce...
Q01 of 03SENIOR

Explain how worker mode in FrankenPHP improves performance.

ANSWER
Worker mode loads the PHP script once and reuses it for all requests, eliminating the overhead of bootstrapping frameworks on each request. This reduces latency and resource usage.
FAQ · 5 QUESTIONS

Frequently Asked Questions

01
What is the difference between FrankenPHP and PHP-FPM?
02
Can I use FrankenPHP with existing PHP applications?
03
Does FrankenPHP support OPcache?
04
How do I debug memory leaks in worker mode?
05
Is FrankenPHP production-ready?
N
Naren Founder & Principal Engineer

20+ years shipping production PHP systems at scale. Drawn from code that ran under real load.

Follow
Verified
production tested
July 13, 2026
last updated
2,073
articles · all by Naren
🔥

That's Advanced PHP. Mark it forged?

3 min read · try the examples if you haven't

Previous
PHP Deployment with Docker and CI/CD
26 / 29 · Advanced PHP
Next
PSR Standards and PHP-FIG: Writing Interoperable PHP