
FrankenPHP Symfony: 3× Performance Boost Guide
PHP-FPM has served us well for years, but it carries a fundamental limitation: every request triggers a full framework bootstrap. FrankenPHP takes a different approach by keeping your Symfony application warm in memory, eliminating that repeated startup cost. The results? Published benchmarks show 3× throughput improvements and latency drops from 45ms to 8ms.
This guide covers what FrankenPHP worker mode is, how to set it up with Symfony, what the benchmarks actually show (and what they don't), and the operational realities you need to prepare for. This comprehensive look at FrankenPHP Symfony integration will help you understand the PHP-FPM vs FrankenPHP tradeoffs.
Prerequisites
Before getting started, make sure you have:
- PHP 8.2 or higher
- Symfony 7.2 (Symfony 7.4 recommended for native support)
- Docker (recommended) or a compatible hosting environment
- Basic familiarity with Symfony's runtime component
What Is FrankenPHP and Why Does It Matter?
FrankenPHP embeds PHP directly into a Go-based server built on Caddy. Created by Kévin Dunglas (the person behind API Platform), it replaces the traditional Nginx PHP-FPM setup with a single application server. This Caddy PHP server approach simplifies deployment significantly.
As of May 2025, FrankenPHP is officially supported by the PHP Foundation, with the codebase hosted under the PHP organization. It has over 8,000 GitHub stars and more than 100 contributors.
The key feature we're focusing on is worker mode. Here's the difference:
Traditional PHP-FPM:
- Spawns new PHP processes per request
- Framework bootstraps on every request
- Database and cache connections re-establish each time
FrankenPHP Worker Mode:
- Maintains persistent workers handling multiple requests
- Boots Symfony once and keeps the kernel warm
- Database and Redis connections stay open between requests
This architectural shift produces four specific gains:
- No bootstrap overhead per request
- Persistent database and cache connections (PHP persistent connections)
- OPcache bytecode stays warm in memory
- Fewer file system operations
Understanding the Benchmarks
Before implementing anything, let's look at what the FrankenPHP benchmarks actually show.
Primary Benchmark Data (AWS t3.medium, January 2026)
Test conditions: Symfony 7.4 JSON API endpoint, k6 load testing
Nginx PHP-FPM: ~1,240 RPS throughput, ~45ms p95 Latency
FrankenPHP Worker Mode: ~3,850 RPS throughput, ~8ms p95 Latency
Difference: 3.1× higher throughput, 5.6× faster latency
These numbers come from a single-author published benchmark. They're plausible given the elimination of bootstrapping overhead, but your results will vary based on your actual workload.
Why Performance Claims Vary So Much
You'll see wildly different numbers across sources:
- Published API benchmarks: 3× throughput, 5-6× latency improvement
- Raspberry Pi 5 community tests: 12-19× improvement
- Conference presentations: "15× faster"
These aren't necessarily contradictory. The improvement depends heavily on how much of your request time is spent on framework bootstrap versus actual work like database queries or external API calls. CPU-bound endpoints with minimal framework overhead will see smaller gains than endpoints that are bootstrap-heavy.
Our experience shows that applications with heavy Doctrine bootstrap performance issues or complex service containers see the most dramatic improvements. Simple endpoints that are already well-cached show more modest gains.
Implementation Steps
This section covers the Symfony 7.4 FrankenPHP setup process step by step.
Step 1: Choose Your Symfony Version Strategy
Symfony 7.4 : Native FrankenPHP support is built in. No additional packages required.
Symfony 7.2-7.3: You'll need the runtime package:
composer require runtime/frankenphp-symfony
The Symfony runtime component handles the integration automatically.
Step 2: Set Up Your Environment Configuration
Add this to your environment variables:
APP_RUNTIME=Runtime\FrankenPhpSymfony\Runtime
For Symfony 7.4 , this step may be unnecessary depending on your setup, as the framework handles detection automatically.
Step 3: Configure FrankenPHP Worker Mode
Your Caddyfile (or equivalent configuration) needs to point to your Symfony front controller:
{
frankenphp
}
localhost {
root * /app/public
php_server {
worker /app/public/index.php
}
}Step 4: Run FrankenPHP
For local development:
frankenphp php-server --worker public/index.php
With FrankenPHP Docker:
docker run -v $PWD:/app -p 80:80 dunglas/frankenphp
Step 5: Configure Worker Restart Limits
The runtime exposes a frankenphp_loop_max setting that controls how many requests each worker handles before restarting:
FRANKENPHP_LOOP_MAX=500
The default is 500 requests. Set to -1 to never restart workers. This setting exists to help manage memory leaks in long-running processes.
Common Mistakes to Avoid
Understanding PHP memory leak worker mode issues is essential for successful deployment.
1. Forgetting That Workers Are Long-Lived
The biggest mindset shift: you're no longer in a share-nothing per-request model. Static variables and service state persist between requests.
Problem code:
class RequestCounter
{
private static int $count = 0;
public function increment(): int
{
return self::$count;
}
}This counter will accumulate across requests, which is probably not what you want.
2. Ignoring Memory Leaks
What's a minor annoyance in PHP-FPM becomes critical in worker mode. A service that leaks 1MB per request will eventually crash your worker.
We've found that the most common leak sources are:
- Event listeners that accumulate data
- Services caching request-specific data
- Doctrine entities held in memory
Use Symfony's kernel.reset tag for services that need cleanup between requests:
services:
App\Service\RequestScopedService:
tags:
- { name: 'kernel.reset', method: 'reset' }The kernel.reset Symfony mechanism is crucial for proper Symfony performance optimization in worker mode.
3. Expecting Hot Reload in Development
Because the application stays in memory, code changes won't take effect immediately. You'll need a file watcher to restart workers during development:
frankenphp php-server --worker public/index.php --watch
Check the FrankenPHP documentation for the exact watch configuration that fits your setup.
4. Not Testing for State Leakage
Before deploying worker mode to production, run sequential requests through your endpoints and verify that request A doesn't affect request B's results.
Testing and Verification
Verify Worker Mode Is Active
Check your response headers or logs. FrankenPHP adds specific headers indicating worker mode is active.
Load Test Before and After
Run comparative load tests with a tool like k6 or wrk:
# Baseline with PHP-FPM k6 run --vus 50 --duration 30s load-test.js # After switching to FrankenPHP k6 run --vus 50 --duration 30s load-test.js
Compare throughput and p95 latency. You should see improvements, though the magnitude depends on your specific endpoints.
Monitor Memory Usage
Watch your worker memory consumption over time:
docker stats
If memory climbs steadily, you have a leak to track down or need to lower your frankenphp_loop_max value.
Check for State Leakage
Create a test that sends multiple sequential requests and verifies isolation:
public function testRequestIsolation(): void
{
// First request sets a value
$this->client->request('POST', '/session/set', ['value' => 'first']);
// New client session
$newClient = static::createClient();
// Should not see the previous value
$newClient->request('GET', '/session/get');
$this->assertNotEquals('first', $newClient->getResponse()->getContent());
}When FrankenPHP Makes Sense (and When It Doesn't)
Good candidates:
- API endpoints with significant framework bootstrap overhead
- Applications where infrastructure costs are a concern
- Projects already comfortable with containerized deployments
Less ideal:
- Applications that are already heavily tuned with OPcache preloading (gains will be smaller)
- Legacy code with static state assumptions that would require significant refactoring
- Teams without experience managing long-running processes
Conclusion
FrankenPHP worker mode offers real performance gains for Symfony applications by eliminating per-request framework bootstrapping. Published benchmarks show 3× throughput improvements and 5-6× latency reductions, though your specific results depend on your workload characteristics.
The tradeoff is operational complexity. You're moving from PHP's traditional share-nothing model to persistent processes, which means paying more attention to memory management and state isolation.
Our approach involves starting with non-critical endpoints in staging, measuring the actual gains for your specific use case, and gradually expanding once you've confirmed your services handle the worker lifecycle correctly.
Implementing FrankenPHP requires careful attention to service configuration, memory management, and deployment infrastructure. If you're evaluating whether worker mode fits your Symfony application, or you've run into issues with memory leaks and state management during migration, we can help you assess your codebase and plan an implementation approach that accounts for your specific architecture.
