# Laravel Octane in 2026: Swoole, RoadRunner and Performance Optimization > Master Laravel Octane 2.19 with FrankenPHP, Swoole and RoadRunner. Learn server selection, memory leak prevention, concurrent tasks, and production deployment patterns for high-performance PHP applications. - Published: 2026-09-11 - Updated: 2026-09-11 - Author: Anthony Fillion-Maillet - Tags: laravel, octane, swoole, roadrunner, frankenphp, performance, php - Reading time: 12 min --- Laravel Octane transforms PHP application performance by keeping the framework bootstrapped in memory across requests. Version 2.19 (August 2026) supports three production-ready servers: [FrankenPHP](https://frankenphp.dev/), [Swoole](https://github.com/swoole/swoole-src), and [RoadRunner](https://roadrunner.dev/). Each serves different operational requirements, and selecting the wrong one costs either performance or stability. > **Server Selection Quick Reference** > > FrankenPHP: simplest setup, HTTP/3 native, single binary. RoadRunner: battle-tested Go binary, no PHP extensions required. Swoole: maximum throughput with coroutine concurrency, requires PECL extension management. ## How Octane Eliminates Bootstrap Overhead Traditional PHP-FPM spawns a new process per request. Laravel boots its service container, loads configuration, registers providers, and resolves middleware on every single HTTP call. Octane inverts this model: the application boots once per worker, then the same instance handles thousands of requests sequentially. The performance gain comes from removing repeated work. A typical Laravel 12 application spends 15 to 40ms bootstrapping before executing business logic. With Octane, that cost drops to zero after the first request. Benchmarks on identical hardware show 2x to 4x throughput improvements for API endpoints, with the gap widening as application complexity increases. ```php // config/octane.php return [ 'server' => env('OCTANE_SERVER', 'frankenphp'), // Workers handle HTTP requests 'workers' => env('OCTANE_WORKERS', 8), // Gracefully restart workers after N requests to prevent memory bloat 'max_requests' => env('OCTANE_MAX_REQUESTS', 500), // Task workers for Swoole concurrent operations 'task_workers' => env('OCTANE_TASK_WORKERS', 6), // Maximum execution time per request in seconds 'max_execution_time' => 30, ]; ``` The `max_requests` setting acts as a safety valve. Even well-written code can accumulate memory over hundreds of requests. Setting this to 500 forces workers to restart before memory pressure becomes problematic. ## FrankenPHP: The 2026 Default for New Projects [FrankenPHP](https://frankenphp.dev/) ships as a single binary built on Caddy and PHP. It handles HTTPS certificates automatically through Let's Encrypt, supports HTTP/3 out of the box, and requires no PHP extensions to install. Laravel's installer and production examples now default to FrankenPHP. ```bash # Install Octane with FrankenPHP composer require laravel/octane php artisan octane:install --server=frankenphp # Start the server php artisan octane:start --server=frankenphp --host=0.0.0.0 --port=8000 ``` For containerized deployments, FrankenPHP provides official Docker images: ```dockerfile # Dockerfile FROM dunglas/frankenphp RUN install-php-extensions \ pcntl \ pdo_pgsql \ redis COPY . /app ENTRYPOINT ["php", "artisan", "octane:frankenphp"] ``` The Docker Compose configuration for development enables HTTPS and HTTP/3: ```yaml # compose.yaml services: frankenphp: build: context: . entrypoint: php artisan octane:frankenphp --workers=1 --max-requests=1 ports: - "443:443" - "443:443/udp" volumes: - .:/app ``` FrankenPHP handles worker restarts gracefully and integrates directly with Caddy's middleware system for advanced routing scenarios. ## RoadRunner: Go-Based Stability Without Extension Dependencies [RoadRunner](https://roadrunner.dev/) compiles to a Go binary that manages PHP worker processes. It communicates with PHP over a binary protocol, keeping worker management and HTTP handling in Go while executing application code in PHP. This architecture provides process isolation: a crashing PHP worker does not affect the Go parent or sibling workers. ```bash # Install Octane with RoadRunner composer require laravel/octane spiral/roadrunner-cli spiral/roadrunner-http php artisan octane:install --server=roadrunner # Download the RoadRunner binary ./vendor/bin/rr get-binary # Start the server php artisan octane:start --server=roadrunner --host=0.0.0.0 --port=8000 ``` RoadRunner configuration lives in `.rr.yaml` at the project root: ```yaml # .rr.yaml version: "3" server: command: "php artisan octane:start --server=roadrunner --host=0.0.0.0 --port=8000" relay: pipes http: address: 0.0.0.0:8000 middleware: ["headers", "gzip"] pool: num_workers: 8 max_jobs: 500 supervisor: max_worker_memory: 128 ``` The `max_worker_memory` setting (in MB) terminates workers that exceed memory limits, providing an additional safety net beyond `max_requests`. For teams with established CI pipelines and existing PHP-FPM infrastructure, RoadRunner offers the smoothest migration path. ## Swoole: Maximum Throughput with Coroutine Concurrency [Swoole](https://github.com/swoole/swoole-src) operates as a PHP extension that replaces the standard request lifecycle with an event-driven, coroutine-capable runtime. It provides features unavailable in other servers: concurrent task execution, ticks and intervals, and an in-memory cache with 2 million operations per second throughput. ```bash # Install Swoole via PECL pecl install swoole # Add to php.ini echo "extension=swoole.so" >> $(php --ini | grep "Loaded Configuration" | cut -d: -f2 | xargs)/php.ini # Install Octane with Swoole composer require laravel/octane php artisan octane:install --server=swoole # Start with task workers for concurrent operations php artisan octane:start --server=swoole --workers=8 --task-workers=6 ``` Swoole's concurrent task execution handles parallel I/O operations within a single request: ```php // app/Http/Controllers/DashboardController.php use App\Models\User; use App\Models\Order; use App\Models\Analytics; use Laravel\Octane\Facades\Octane; class DashboardController extends Controller { public function index() { // Execute three queries concurrently instead of sequentially [$users, $orders, $analytics] = Octane::concurrently([ fn () => User::active()->count(), fn () => Order::today()->sum('total'), fn () => Analytics::hourly()->get(), ]); return view('dashboard', compact('users', 'orders', 'analytics')); } } ``` Without `Octane::concurrently()`, these three queries execute sequentially: 50ms + 30ms + 40ms = 120ms total. With concurrent execution, the total time equals the slowest query: 50ms. The `--task-workers` flag controls how many concurrent operations can run simultaneously across all request workers. > **Swoole-Only Features** > > Concurrent tasks, ticks, intervals, Octane cache, and Swoole tables require the Swoole extension. FrankenPHP and RoadRunner do not support these features. Evaluate whether your application needs them before committing to Swoole's operational complexity. ## Memory Leak Prevention: Writing Stateless Code Octane's performance comes from persistent state, which creates the primary challenge: code that worked fine under PHP-FPM can leak memory catastrophically under Octane. The application instance survives across requests, so static properties, singletons, and global state accumulate. Three patterns cause most memory leaks: **Static arrays that grow unbounded:** ```php // WRONG: Memory leak - array grows with every request class MetricsCollector { public static array $data = []; public static function record(string $metric): void { self::$data[] = $metric; // Never cleared between requests } } // CORRECT: Use request-scoped storage or external systems class MetricsCollector { public function record(string $metric): void { Redis::lpush('metrics', $metric); // External storage } } ``` **Singletons holding request-specific data:** ```php // WRONG: First request's user leaks into subsequent requests $this->app->singleton(UserContext::class, function ($app) { return new UserContext($app['request']->user()); }); // CORRECT: Use closures for deferred resolution $this->app->singleton(UserContext::class, function ($app) { return new UserContext(fn () => $app['request']->user()); }); ``` **Container and request injection in constructors:** ```php // WRONG: Captures stale container/request class PaymentService { public function __construct( private Application $app, private Request $request ) {} } // CORRECT: Inject via method parameters or use helpers class PaymentService { public function processPayment(Request $request): void { $user = $request->user(); $config = config('services.stripe.key'); // Global helper, always fresh } } ``` The global helpers `app()`, `request()`, and `config()` always return current values. Using them instead of constructor injection sidesteps most singleton-related leaks. ## Production Deployment Patterns Octane servers need process supervision to restart on crashes. Supervisor handles this reliably: ```ini ; /etc/supervisor/conf.d/octane.conf [program:octane] process_name=%(program_name)s_%(process_num)02d command=php /var/www/app/artisan octane:start --server=frankenphp --host=127.0.0.1 --port=8000 autostart=true autorestart=true user=www-data redirect_stderr=true stdout_logfile=/var/www/app/storage/logs/octane.log stopwaitsecs=3600 ``` For deployments, Octane workers must reload to pick up new code. The `octane:reload` command handles this gracefully, waiting for in-flight requests to complete before recycling workers: ```bash # In deployment script (after git pull, composer install, etc.) php artisan octane:reload ``` Nginx sits in front of Octane to serve static assets and terminate SSL: ```nginx # /etc/nginx/sites-available/app.conf upstream octane { server 127.0.0.1:8000; keepalive 32; } server { listen 443 ssl http2; server_name example.com; root /var/www/app/public; ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem; location / { try_files $uri $uri/ @octane; } location @octane { proxy_http_version 1.1; proxy_set_header Host $http_host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header Connection ""; proxy_pass http://octane; } } ``` The `keepalive` directive maintains persistent connections between Nginx and Octane, reducing TCP handshake overhead for proxied requests. ## Server Selection Decision Tree Choosing between FrankenPHP, RoadRunner, and Swoole depends on operational constraints and application requirements: | Requirement | Recommended Server | |-------------|-------------------| | Simplest setup, containerized deployment | FrankenPHP | | HTTP/3 support out of the box | FrankenPHP | | No PHP extension dependencies | FrankenPHP or RoadRunner | | Maximum process isolation | RoadRunner | | Established PHP-FPM infrastructure | RoadRunner | | Concurrent I/O within requests | Swoole | | In-memory caching (2M ops/sec) | Swoole | | Ticks, intervals, background tasks | Swoole | | Swoole tables for shared state | Swoole | For teams starting new projects in 2026, FrankenPHP provides the best balance of simplicity and capability. Migrating existing applications works best with RoadRunner due to its process isolation and familiar deployment model. Swoole suits high-throughput APIs that benefit from coroutine concurrency and require Swoole-specific features. ## Interview Questions on Laravel Octane Technical interviews increasingly cover Octane as high-traffic Laravel applications adopt it. Common questions and what interviewers look for: **"How does Octane achieve its performance gains?"** The expected answer: Octane boots Laravel once per worker and reuses that instance across requests, eliminating the 15-40ms bootstrap cost on each request. Candidates should mention that service providers' `register` and `boot` methods run once per worker, not per request. **"What causes memory leaks in Octane applications?"** Strong answers identify three sources: static arrays that accumulate data, singletons capturing request-specific state, and constructor injection of the container or request objects. Candidates should explain that the fix involves using closures for deferred resolution or passing data through method parameters. **"When would you choose Swoole over RoadRunner?"** Swoole's advantages are coroutine concurrency via `Octane::concurrently()`, the high-speed Octane cache, and features like ticks and intervals. The tradeoff is operational complexity: Swoole requires managing a PECL extension, while RoadRunner ships as a Go binary. For CRUD applications without concurrent I/O needs, RoadRunner is simpler to operate. For more Laravel interview preparation, see [PHP Laravel Developer Interview Questions 2026](/blog/laravel/php-laravel-developer-interview-questions-2026). **"How do you handle deployments with Octane?"** The deployment sequence: stop accepting new connections, wait for in-flight requests to complete, reload workers with new code. The `php artisan octane:reload` command handles this. Candidates should mention Supervisor or systemd for process supervision and Nginx or Caddy as a reverse proxy. ## Monitoring and Profiling Octane Applications Memory monitoring becomes critical under Octane. Workers persist, so memory growth indicates leaks rather than normal request overhead. Laravel Pulse and Telescope both work with Octane and provide request-level profiling. ```php // app/Providers/AppServiceProvider.php use Laravel\Octane\Events\RequestReceived; use Laravel\Octane\Events\RequestTerminated; public function boot(): void { Event::listen(RequestReceived::class, function ($event) { $event->sandbox->instance('request.memory.start', memory_get_usage()); }); Event::listen(RequestTerminated::class, function ($event) { $start = $event->sandbox->make('request.memory.start'); $delta = memory_get_usage() - $start; if ($delta > 1024 * 1024) { // More than 1MB growth Log::warning('High memory delta', [ 'path' => $event->request->path(), 'delta_mb' => round($delta / 1024 / 1024, 2), ]); } }); } ``` This event listener logs requests that increase memory by more than 1MB, helping identify endpoints that need optimization. For production monitoring, external APM tools like Datadog or New Relic provide worker-level visibility. ## Integrating Octane with Queues and Horizon Octane handles HTTP requests; [Laravel Horizon](https://laravel.com/docs/12.x/horizon) manages queue workers. The two operate independently and complement each other. Heavy processing belongs in queued jobs, keeping Octane workers responsive. ```php // app/Http/Controllers/ReportController.php use App\Jobs\GenerateReport; class ReportController extends Controller { public function generate(Request $request) { // Dispatch to queue instead of blocking the Octane worker GenerateReport::dispatch($request->user(), $request->input('parameters')); return response()->json(['status' => 'processing']); } } ``` For related patterns on background processing, see [Laravel Queues and Jobs: Asynchronous Architecture](/blog/laravel/laravel-queues-jobs-architecture) and [Laravel Middleware Deep Dive](/blog/laravel/laravel-middleware-deep-dive). ## What to Remember About Laravel Octane in 2026 - Octane 2.19 supports three servers: FrankenPHP (simplest), RoadRunner (most stable), and Swoole (most features) - Performance gains come from eliminating per-request bootstrap; expect 2x to 4x throughput improvement - Memory leaks occur because worker state persists; avoid static arrays, constructor-injected requests, and singletons with request data - Use `--max-requests=500` to force worker recycling before memory pressure builds - Swoole-only features include `Octane::concurrently()`, ticks, intervals, and the Octane cache - Deploy behind Nginx or Caddy with Supervisor for process management - Profile memory growth per request; deltas over 1MB indicate potential leaks - Queue heavy work through Horizon; keep Octane workers handling fast HTTP responses --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/en/blog/laravel/laravel-octane-swoole-roadrunner-performance-optimization